diff --git a/README.md b/README.md index 9d394a4..52eaa15 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ NEXT_PUBLIC_WORKOS_REDIRECT_URI="http://localhost:3000/callback" # configured in openssl rand -base64 24 ``` -To use the `signOut` method, you'll need to set your app's homepage in your WorkOS dashboard settings under "Redirects". +To use the `signOut` method, you'll need to set a default Logout URI in your WorkOS dashboard settings under "Redirects". ### Optional configuration @@ -336,7 +336,13 @@ export const config = { matcher: ['/', '/account/:path*'] }; ### Signing out -Use the `signOut` method to sign out the current logged in user and redirect to your app's homepage. The homepage redirect is set in your WorkOS dashboard settings under "Redirect". +Use the `signOut` method to sign out the current logged in user and redirect to your app's default Logout URI. The Logout URI is set in your WorkOS dashboard settings under "Redirect". + +To use a non-default Logout URI, you can use the `returnTo` parameter. + +```tsx +await signOut({ returnTo: 'https://your-app.com/signed-out' }); +``` ### Visualizing an impersonation diff --git a/__tests__/auth.spec.ts b/__tests__/auth.spec.ts index 9f7c287..64635b0 100644 --- a/__tests__/auth.spec.ts +++ b/__tests__/auth.spec.ts @@ -1,10 +1,18 @@ import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { getSignInUrl, getSignUpUrl, signOut } from '../src/auth.js'; +import * as session from '../src/session'; // These are mocked in jest.setup.ts import { cookies, headers } from 'next/headers'; import { redirect } from 'next/navigation'; +import { UserInfo } from '../src/interfaces'; + +jest.mock('../src/session', () => { + const actual = jest.requireActual('../src/session'); + + return { ...actual, terminateSession: jest.fn(actual.terminateSession) }; +}); describe('auth.ts', () => { beforeEach(async () => { @@ -73,5 +81,32 @@ describe('auth.ts', () => { const sessionCookie = nextCookies.get('wos-session'); expect(sessionCookie).toBeUndefined(); }); + + describe('when given a `returnTo` parameter', () => { + it('passes the `returnTo` through to `terminateSession`', async () => { + const nextHeaders = await headers(); + + nextHeaders.set('x-workos-middleware', 'true'); + + await signOut({ returnTo: 'https://example.com/signed-out' }); + + expect(redirect).toHaveBeenCalledTimes(1); + expect(redirect).toHaveBeenCalledWith('https://example.com/signed-out'); + expect(session.terminateSession).toHaveBeenCalledWith({ returnTo: 'https://example.com/signed-out' }); + }); + + describe('when there is no session', () => { + it('returns to the `returnTo`', async () => { + const nextHeaders = await headers(); + + nextHeaders.set('x-workos-middleware', 'true'); + + await signOut({ returnTo: 'https://example.com/signed-out' }); + + expect(redirect).toHaveBeenCalledTimes(1); + expect(redirect).toHaveBeenCalledWith('https://example.com/signed-out'); + }); + }); + }); }); }); diff --git a/__tests__/session.spec.ts b/__tests__/session.spec.ts index 3897012..c1b4e75 100644 --- a/__tests__/session.spec.ts +++ b/__tests__/session.spec.ts @@ -778,5 +778,26 @@ describe('session.ts', () => { expect(redirect).toHaveBeenCalledTimes(1); expect(redirect).toHaveBeenCalledWith('/'); }); + + describe('when given a `returnTo` URL', () => { + it('includes a `return_to` query parameter in the logout URL', async () => { + const nextHeaders = await headers(); + nextHeaders.set('x-url', 'http://example.com/protected'); + + mockSession.accessToken = await generateTestToken(); + + nextHeaders.set( + 'x-workos-session', + await sealData(mockSession, { password: process.env.WORKOS_COOKIE_PASSWORD as string }), + ); + + await terminateSession({ returnTo: 'http://example.com/signed-out' }); + + expect(redirect).toHaveBeenCalledTimes(1); + expect(redirect).toHaveBeenCalledWith( + 'https://api.workos.com/user_management/sessions/logout?session_id=session_123&return_to=http%3A%2F%2Fexample.com%2Fsigned-out', + ); + }); + }); }); }); diff --git a/package-lock.json b/package-lock.json index 8317db5..e08b9af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.2", "license": "MIT", "dependencies": { - "@workos-inc/node": "^7.33.0", + "@workos-inc/node": "^7.37.0", "iron-session": "^8.0.1", "jose": "^5.2.3", "path-to-regexp": "^6.2.2" @@ -2434,9 +2434,9 @@ "dev": true }, "node_modules/@workos-inc/node": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-7.33.0.tgz", - "integrity": "sha512-xdAJ3Gozda6XnNxn7/3JYGYxqpQMPKc1lvE9uye5u+F7Gnp4uLo2zvm4OCSk5V189UfFqrnKTnOUv2j2q7CPtA==", + "version": "7.37.0", + "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-7.37.0.tgz", + "integrity": "sha512-8RIPIlzKQjV848NdiTfEg3++iI0Z5zvt8ld3X3kUrIA0TuK0V/UVPR+Y4sz2cEtqxlz3eWAlqx8jwHrASxFb0w==", "dependencies": { "iron-session": "~6.3.1", "jose": "~5.6.3", diff --git a/package.json b/package.json index ac71dd6..a2e8e18 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "format": "prettier \"{src,__tests__}/**/*.{js,ts,tsx}\" --write" }, "dependencies": { - "@workos-inc/node": "^7.33.0", + "@workos-inc/node": "^7.37.0", "iron-session": "^8.0.1", "jose": "^5.2.3", "path-to-regexp": "^6.2.2" diff --git a/src/auth.ts b/src/auth.ts index 875d80f..b5443d9 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -13,7 +13,7 @@ async function getSignUpUrl() { return getAuthorizationUrl({ screenHint: 'sign-up' }); } -async function signOut() { +async function signOut({ returnTo }: { returnTo?: string } = {}) { const cookie: { name: string; domain?: string } = { name: WORKOS_COOKIE_NAME || 'wos-session', }; @@ -22,7 +22,7 @@ async function signOut() { const nextCookies = await cookies(); nextCookies.delete(cookie); - await terminateSession(); + await terminateSession({ returnTo }); } export { getSignInUrl, getSignUpUrl, signOut }; diff --git a/src/session.ts b/src/session.ts index b168b2c..436f191 100644 --- a/src/session.ts +++ b/src/session.ts @@ -382,12 +382,12 @@ async function withAuth({ ensureSignedIn = false }: { ensureSignedIn?: boolean } }; } -async function terminateSession() { +async function terminateSession({ returnTo }: { returnTo?: string } = {}) { const { sessionId } = await withAuth(); if (sessionId) { - redirect(workos.userManagement.getLogoutUrl({ sessionId })); + redirect(workos.userManagement.getLogoutUrl({ sessionId, returnTo })); } else { - redirect('/'); + redirect(returnTo ?? '/'); } }