Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default async function login(req, res) {
}
```

> Note: This route supports providing `redirectTo` in the querystring, eg: (`/api/login?redirectTo=/profile`). The user will automatically be redirect to this URL after signing in.
> Note: This route supports providing `redirectTo` in the query string, eg: (`/api/login?redirectTo=/profile`). The user will automatically be redirect to this URL after signing in.

This will redirect the user to Auth0. After the transaction is completed Auth0 will redirect the user back to your application. This is why the callback route (`/pages/api/callback.js`) needs to be created which will create a session cookie:

Expand Down Expand Up @@ -204,6 +204,8 @@ export default async function logout(req, res) {
}
```

Note that the third parameter of `handleLogout` accepts an optional `returnTo` to allow request-time configuration of where to redirect the user to on logout.

### User Profile

If you want to expose a route which returns the user profile to the client you can create an additional route (eg: `/pages/api/me.js`):
Expand Down
19 changes: 10 additions & 9 deletions src/handlers/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { IOidcClientFactory } from '../utils/oidc-client';
import CookieSessionStoreSettings from '../session/cookie-store/settings';
import { ISessionStore } from '../session/store';

function createLogoutUrl(settings: IAuth0Settings): string {
return (
`https://${settings.domain}/v2/logout?` +
`client_id=${settings.clientId}` +
`&returnTo=${encodeURIComponent(settings.postLogoutRedirectUri)}`
);
export interface LogoutOptions {
returnTo?: string;
}

function createLogoutUrl(settings: IAuth0Settings, returnToUrl: string): string {
return `https://${settings.domain}/v2/logout?client_id=${settings.clientId}&returnTo=${returnToUrl}`;
}

export default function logoutHandler(
Expand All @@ -20,7 +20,7 @@ export default function logoutHandler(
clientProvider: IOidcClientFactory,
store: ISessionStore
) {
return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
return async (req: NextApiRequest, res: NextApiResponse, options?: LogoutOptions): Promise<void> => {
if (!req) {
throw new Error('Request is not available');
}
Expand All @@ -31,17 +31,18 @@ export default function logoutHandler(

const session = await store.read(req);
let endSessionUrl;
const returnToUrl = encodeURIComponent(options?.returnTo || settings.postLogoutRedirectUri);

try {
const client = await clientProvider();
endSessionUrl = client.endSessionUrl({
id_token_hint: session ? session.idToken : undefined,
post_logout_redirect_uri: encodeURIComponent(settings.postLogoutRedirectUri)
post_logout_redirect_uri: returnToUrl
});
} catch (err) {
if (/end_session_endpoint must be configured/.exec(err)) {
// Use default url if end_session_endpoint is not configured
endSessionUrl = createLogoutUrl(settings);
endSessionUrl = createLogoutUrl(settings, returnToUrl);
} else {
throw err;
}
Expand Down
5 changes: 3 additions & 2 deletions src/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LoginOptions } from './handlers/login';
import { ITokenCache } from './tokens/token-cache';
import { CallbackOptions } from './handlers/callback';
import { ProfileOptions } from './handlers/profile';
import { LogoutOptions } from './handlers/logout';
import { IApiRoute } from './handlers/require-authentication';

export interface ISignInWithAuth0 {
Expand All @@ -19,9 +20,9 @@ export interface ISignInWithAuth0 {
handleCallback: (req: NextApiRequest, res: NextApiResponse, options?: CallbackOptions) => Promise<void>;

/**
* Logout handler which will clear the local session and the Auth0 session
* Logout handler which will clear the local session and the Auth0 session.
*/
handleLogout: (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
handleLogout: (req: NextApiRequest, res: NextApiResponse, options?: LogoutOptions) => Promise<void>;

/**
* Profile handler which return profile information about the user.
Expand Down
33 changes: 30 additions & 3 deletions tests/handlers/logout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { parse } from 'cookie';
import { promisify } from 'util';

import HttpServer from '../helpers/server';
import logout from '../../src/handlers/logout';
import logout, { LogoutOptions } from '../../src/handlers/logout';
import { withoutApi } from '../helpers/default-settings';
import CookieSessionStoreSettings from '../../src/session/cookie-store/settings';
import { discovery } from '../helpers/oidc-nocks';
Expand Down Expand Up @@ -34,11 +34,18 @@ const sessionStore: ISessionStore = {

describe('logout handler', () => {
let httpServer: HttpServer;
let logoutHandler: any;
let logoutOptions: LogoutOptions | null;

beforeEach((done) => {
httpServer = new HttpServer(
logout(withoutApi, new CookieSessionStoreSettings(withoutApi.session), getClient(withoutApi), sessionStore)
logoutHandler = logout(
withoutApi,
new CookieSessionStoreSettings(withoutApi.session),
getClient(withoutApi),
sessionStore
);
logoutOptions = {};
httpServer = new HttpServer((req, res) => logoutHandler(req, res, logoutOptions));
httpServer.start(done);
});

Expand All @@ -62,6 +69,26 @@ describe('logout handler', () => {
);
});

test('should return to the custom path', async () => {
const customReturnTo = 'https://www.foo.bar';
logoutOptions = {
returnTo: customReturnTo
};
discovery(withoutApi);

const { statusCode, headers } = await getAsync({
url: httpServer.getUrl(),
followRedirect: false
});

expect(statusCode).toBe(302);
expect(headers.location).toBe(
`https://${withoutApi.domain}/v2/logout?client_id=${withoutApi.clientId}&returnTo=${encodeURIComponent(
customReturnTo
)}`
);
});

test('should use end_session_endpoint if available', async () => {
discovery(withoutApi, { end_session_endpoint: 'https://my-end-session-endpoint/logout' });

Expand Down