Skip to content

Commit

Permalink
Fix post logout redirect, add config for default
Browse files Browse the repository at this point in the history
  • Loading branch information
balazsorban44 authored and joshcanhelp committed Jan 23, 2020
1 parent 5ef59b1 commit 68d6190
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"indent": [
"error",
2,
{ "SwitchCase": 1 }
{ "SwitchCase": 1 }
],
"linebreak-style": [
"error",
Expand Down
1 change: 1 addition & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Additional configuration keys that can be passed to `auth()` on initialization:
- **`loginPath`** - Relative path to application login. Default is `/login`.
- **`logoutPath`** - Relative path to application logout. Default is `/logout`.
- **`redirectUriPath`** - Relative path to the application callback to process the response from the authorization server. This value is combined with the `baseUrl` and sent to the authorize endpoint as the `redirectUri` parameter. Default is `/callback`.
- **`postLogoutRedirectUri`** - Either a relative path to the application or a valid URI to an external domain. The user will be redirected to this after a logout has been performed. Default is `baseUrl`.
- **`required`** - Use a boolean value to require authentication for all routes. Pass a function instead to base this value on the request. Default is `true`.
- **`routes`** - Boolean value to automatically install the login and logout routes. See [the examples](EXAMPLES.md) for more information on how this key is used. Default is `true`.

Expand Down
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ interface ConfigParams {
*/
redirectUriPath?: string;

/**
* Either a relative path to the application
* or a valid URI to an external domain.
* The user will be redirected to this after a logout has been performed.
*/
postLogoutRedirectUri?: string;

/**
* Require authentication for all routes.
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const paramsSchema = Joi.object().keys({
redirectUriPath: Joi.string().uri({relativeOnly: true}).optional().default('/callback'),
required: Joi.alternatives([ Joi.func(), Joi.boolean()]).optional().default(true),
routes: Joi.boolean().optional().default(true),
postLogoutRedirectUri: Joi.string().uri({allowRelative: true}).optional().default('/')
});

function buildAuthorizeParams(authorizationParams) {
Expand Down Expand Up @@ -119,7 +120,6 @@ module.exports.get = function(params) {
}

config = paramsValidation.value;

config.authorizationParams = buildAuthorizeParams(config.authorizationParams);
config.appSessionCookie = buildAppSessionCookieConfig(config.appSessionCookie);

Expand Down
21 changes: 16 additions & 5 deletions lib/context.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const cb = require('cb');
const url = require('url');
const urlJoin = require('url-join');
const transient = require('./transientHandler');
const { get: getClient } = require('./client');
Expand Down Expand Up @@ -88,24 +89,34 @@ class ResponseContext {
const next = cb(this._next).once();
const req = this._req;
const res = this._res;
const returnURL = params.returnTo || this._config.baseURL;

let returnURL = params.returnTo || req.query.returnTo || this._config.postLogoutRedirectUri;

if (url.parse(returnURL).host === null) {
returnURL = urlJoin(this._config.baseURL, returnURL);
}

if (!req.isAuthenticated()) {
return res.redirect(returnURL);
}

req[this._config.appSessionName] = undefined;

if (!this._config.idpLogout) {
return res.redirect(returnURL);
}

const client = this._req.openid.client;
try {
const client = this._req.openid.client;
const url = client.endSessionUrl({
returnURL = client.endSessionUrl({
post_logout_redirect_uri: returnURL,
id_token_hint: req.openid.tokens,
});
res.redirect(url);
} catch(err) {
next(err);
return next(err);
}

res.redirect(returnURL);
}

}
Expand Down
87 changes: 78 additions & 9 deletions test/logout.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('logout route', function() {

it('should redirect to the base url', function() {
assert.equal(logoutResponse.statusCode, 302);
assert.equal(logoutResponse.headers.location, 'https://example.org');
assert.equal(logoutResponse.headers.location, 'https://example.org/');
});
});

Expand Down Expand Up @@ -83,15 +83,84 @@ describe('logout route', function() {

it('should redirect to the base url', function() {
assert.equal(logoutResponse.statusCode, 302);
const parsedUrl = url.parse(logoutResponse.headers.location, true);
assert.deepInclude(parsedUrl, {
protocol: 'https:',
hostname: 'test.auth0.com',
query: { returnTo: 'https://example.org', client_id: '__test_client_id__' },
pathname: '/v2/logout',
});
assert.equal(logoutResponse.headers.location, 'https://example.org/');
});
});


});
describe('should use postLogoutRedirectUri if present', function() {
describe('should allow relative paths, and prepend with baseURL', () => {
let baseUrl;
const jar = request.jar();

before(async function() {
const middleware = auth({
idpLogout: false,
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
appSessionSecret: '__test_session_secret__',
postLogoutRedirectUri: '/after-logout-in-auth-config',
required: false,
});
baseUrl = await server.create(middleware);
await request.post({
uri: '/session',
json: {
openidTokens: {
id_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
},
baseUrl, jar
});
});

it('should redirect to postLogoutRedirectUri in auth() config', async function() {
const logoutResponse = await request.get({uri: '/logout', baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://example.org/after-logout-in-auth-config');
});

it('should redirect to returnTo in logout query', async function() {
const logoutResponse = await request.get({uri: '/logout', qs: {returnTo: '/after-logout-in-logout-query'}, baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://example.org/after-logout-in-logout-query');
});
});

describe('should allow absolute paths', () => {
let baseUrl;
const jar = request.jar();

before(async function() {
const middleware = auth({
idpLogout: false,
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
appSessionSecret: '__test_session_secret__',
postLogoutRedirectUri: 'https://external-domain.com/after-logout-in-auth-config',
required: false,
});
baseUrl = await server.create(middleware);
await request.post({
uri: '/session',
json: {
openidTokens: {
id_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
},
baseUrl, jar
});
});

it('should redirect to postLogoutRedirectUri in auth() config', async function() {
const logoutResponse = await request.get({uri: '/logout', baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://external-domain.com/after-logout-in-auth-config');
});

it('should redirect to returnTo in logout query', async function() {
const logoutResponse = await request.get({uri: '/logout', qs: {returnTo: 'https://external-domain.com/after-logout-in-logout-query'}, baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://external-domain.com/after-logout-in-logout-query');
});
});
});
});

0 comments on commit 68d6190

Please sign in to comment.