Skip to content

Commit

Permalink
Merge pull request #12 from novomatic-tech/api-logout
Browse files Browse the repository at this point in the history
Add api logout endpoint for non-browser invocation (without redirection)
  • Loading branch information
tomnocon authored Jan 13, 2021
2 parents 5af3e70 + d5a00b4 commit f34798c
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Parameter | Description | Default
`minTimeBetweenJwksRequests` | Amount of time, in seconds, specifying minimum interval between two requests to Keycloak to retrieve new public keys. | `10`
`loginUrl` | An URL of the endpoint responsible for obtaining OAuth2.0's Authorization Code grant. It is exposed only if `bearerOnly` is set to false. | `/sso/login`
`logoutUrl` | An URL the endpoint responsible for handling logout procedure. It is exposed only if `bearerOnly` is set to false. | `/sso/logout`
`apilogoutUrl` | An URL the endpoint responsible for handling logout procedure via a non-browser invocation (without redirects). | `/api/logout`
`principalUrl` | An URL of the endpoint exposing resource owner's data (such as its name, ID token, access token etc.). Use `null` in order not to expose this endpoint at all. | `/api/principal`
`principalConversion` | A function which alters principal representation exposed by `principalUrl` endpoint before it's sent in a response. Define this function if you don't want for example an access token to be exposed. | `undefined` (no conversion)
`principalNameAttribute` | An access/ID token attribute which will be used as the principal name (user name). It will fallback to *sub* token attribute in case the *principalNameAttribute* is not present. Possible values are *sub*, *preferred_username*, *email*, *name*. | `name`
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0

* Add api logout endpoint for non-browser invocation (without redirection)

## 1.2.2

* Pass locale query (kc_locale or ui_locale) in logout redirect uri to login page
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "keycloak-hapi",
"version": "2.0.0",
"version": "2.1.0",
"description": "Integration of Keycloak Authorization Server with HapiJS",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -35,7 +35,8 @@
},
"peerDependencies": {
"hapi": "^17.0.0",
"yar": "^9.1.0"
"yar": "^9.1.0",
"wreck": "^14.2.0"
},
"engines": {
"node": ">=8",
Expand Down
43 changes: 42 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const Token = require('keycloak-connect/middleware/auth-utils/token');
const Grant = require('keycloak-connect/middleware/auth-utils/grant');
const UUID = require('keycloak-connect/uuid');
const Boom = require('boom');
const Wreck = require('wreck');
const querystring = require('querystring');
const _ = require('lodash');
const pkg = require('../package.json');
const crypto = require('crypto');
Expand Down Expand Up @@ -232,6 +234,7 @@ class KeycloakAdapter {
this.config = Object.assign({
loginUrl: '/sso/login',
logoutUrl: '/sso/logout',
apiLogout: '/api/logout',
principalUrl: '/api/principal',
corsOrigin: ['*'],
principalConversion: defaultPrincipalConversion,
Expand Down Expand Up @@ -301,6 +304,11 @@ class KeycloakAdapter {
return urljoin(base, this.server.realm.modifiers.route.prefix || '');
}

getClientBasicAuth() {
const clientCredentials = `${this.config.clientId}:${this.config.clientSecret}`;
return Buffer.from(clientCredentials).toString('base64');
}

getLoginRedirectUrl(request) {
return urljoin(this.getBaseUrl(request), this.config.loginUrl, '?auth_callback=1');
}
Expand Down Expand Up @@ -405,6 +413,7 @@ class KeycloakAdapter {
if (!this.config.bearerOnly) {
registerLoginRoute(this);
registerLogoutRoute(this);
registerApiLogoutRoute(this);
registerBackChannelLogoutRoute(this);
}
if (this.config.principalUrl) {
Expand Down Expand Up @@ -521,7 +530,7 @@ const registerLogoutRoute = (keycloak) => {
path: keycloak.config.logoutUrl,
method: 'GET',
handler(request, reply) {
keycloak.server.log(['debug', 'keycloak'], 'Signing out');
keycloak.server.log(['debug', 'keycloak'], 'Signing out using redirection');
const grantStore = keycloak.getGrantStoreByName('session');
grantStore.clearGrant(request);
const locale = getLocale(request);
Expand All @@ -538,6 +547,38 @@ const registerLogoutRoute = (keycloak) => {
});
};

const registerApiLogoutRoute = (keycloak) => {
keycloak.server.route({
path: keycloak.config.apiLogoutUrl,
method: 'POST',
handler: async (request) => {
keycloak.server.log(['debug', 'keycloak'], 'Signing out using refresh token');
const grantStore = keycloak.getGrantStoreFor(request);
const grant = grantStore.getGrant(request);
const logoutUrl = keycloak.getLogoutUrl({});
await Wreck.post(logoutUrl, {
payload: querystring.stringify({
'refresh_token': grant.refresh_token.token,
'client_id': keycloak.config.clientId
}),
headers: {
'content-type': 'application/x-www-form-urlencoded',
'authorization': `Basic ${keycloak.getClientBasicAuth()}`
}
});
grantStore.clearGrant(request);
return {
message: 'User has been successfully logged out.'
}
},
config: {
cors: {
origin: keycloak.config.corsOrigin
}
}
});
};

/* This is a plugin registration backward-compatible with Hapijs v14+ */
const register = (server, options, next) => {
const adapter = new KeycloakAdapter(server, options);
Expand Down

0 comments on commit f34798c

Please sign in to comment.