diff --git a/src/auth0-session/handlers/callback.ts b/src/auth0-session/handlers/callback.ts index 441d679e8..efa4c85b1 100644 --- a/src/auth0-session/handlers/callback.ts +++ b/src/auth0-session/handlers/callback.ts @@ -3,9 +3,10 @@ import { AuthorizationParameters, Config } from '../config'; import TransientStore from '../transient-store'; import { decodeState } from '../utils/encoding'; import { SessionCache } from '../session-cache'; -import { MissingStateCookieError, MissingStateParamError } from '../utils/errors'; +import { MalformedStateCookieError, MissingStateCookieError, MissingStateParamError } from '../utils/errors'; import { Auth0Request, Auth0Response } from '../http'; import { AbstractClient } from '../client/abstract-client'; +import type { AuthVerification } from './login'; function getRedirectUri(config: Config): string { return urlJoin(config.baseURL, config.routes.callback); @@ -34,11 +35,27 @@ export default function callbackHandlerFactory( let tokenResponse; - const expectedState = await transientCookieHandler.read('state', req, res); - if (!expectedState) { + let authVerification: AuthVerification; + const cookie = await transientCookieHandler.read('auth_verification', req, res); + + if (!cookie) { throw new MissingStateCookieError(); } + try { + authVerification = JSON.parse(cookie); + } catch (_) { + throw new MalformedStateCookieError(); + } + + const { + max_age, + code_verifier, + nonce, + state: expectedState, + response_type = config.authorizationParams.response_type + } = authVerification; + let callbackParams: URLSearchParams; try { callbackParams = await client.callbackParams(req, expectedState); @@ -52,11 +69,6 @@ export default function callbackHandlerFactory( if (!callbackParams.get('state')) { throw new MissingStateParamError(); } - const max_age = await transientCookieHandler.read('max_age', req, res); - const code_verifier = await transientCookieHandler.read('code_verifier', req, res); - const nonce = await transientCookieHandler.read('nonce', req, res); - const response_type = - (await transientCookieHandler.read('response_type', req, res)) || config.authorizationParams.response_type; try { tokenResponse = await client.callback( diff --git a/src/auth0-session/handlers/login.ts b/src/auth0-session/handlers/login.ts index fb5700f40..c8ea37ac5 100644 --- a/src/auth0-session/handlers/login.ts +++ b/src/auth0-session/handlers/login.ts @@ -1,6 +1,6 @@ import urlJoin from 'url-join'; import { Config, LoginOptions } from '../config'; -import TransientStore, { StoreOptions } from '../transient-store'; +import TransientStore from '../transient-store'; import { encodeState } from '../utils/encoding'; import createDebug from '../utils/debug'; import { Auth0Request, Auth0Response } from '../http'; @@ -14,6 +14,14 @@ function getRedirectUri(config: Config): string { export type HandleLogin = (req: Auth0Request, res: Auth0Response, options?: LoginOptions) => Promise; +export type AuthVerification = { + nonce: string; + state: string; + max_age?: number; + code_verifier?: string; + response_type?: string; +}; + export default function loginHandlerFactory( config: Config, client: AbstractClient, @@ -35,10 +43,6 @@ export default function loginHandlerFactory( ...(opts.authorizationParams || {}) }; - const transientOpts: Pick = { - sameSite: opts.authorizationParams.response_mode === 'form_post' ? 'none' : config.session.cookie.sameSite - }; - const stateValue = await opts.getLoginState(opts); if (typeof stateValue !== 'object') { throw new Error('Custom state value must be an object.'); @@ -53,48 +57,40 @@ export default function loginHandlerFactory( stateValue.code_verifier = client.generateRandomCodeVerifier(); } - if (responseType !== config.authorizationParams.response_type) { - await transientHandler.save('response_type', req, res, { - ...transientOpts, - value: responseType - }); + const validResponseTypes = ['id_token', 'code id_token', 'code']; + if (!validResponseTypes.includes(responseType)) { + throw new Error(`response_type should be one of ${validResponseTypes.join(', ')}`); + } + if (!/\bopenid\b/.test(opts.authorizationParams.scope as string)) { + throw new Error('scope should contain "openid"'); } - const authParams = { - ...opts.authorizationParams, - nonce: await transientHandler.save('nonce', req, res, { ...transientOpts, value: client.generateRandomNonce() }), - state: await transientHandler.save('state', req, res, { - ...transientOpts, - value: encodeState(stateValue) - }), - ...(usePKCE - ? { - code_challenge: await client.calculateCodeChallenge( - await transientHandler.save('code_verifier', req, res, { - ...transientOpts, - value: client.generateRandomCodeVerifier() - }) - ), - code_challenge_method: 'S256' - } - : undefined) + const authVerification: AuthVerification = { + nonce: client.generateRandomNonce(), + state: encodeState(stateValue) }; - const validResponseTypes = ['id_token', 'code id_token', 'code']; - if (!validResponseTypes.includes(authParams.response_type as string)) { - throw new Error(`response_type should be one of ${validResponseTypes.join(', ')}`); + if (opts.authorizationParams.max_age) { + authVerification.max_age = opts.authorizationParams.max_age; } - if (!/\bopenid\b/.test(authParams.scope as string)) { - throw new Error('scope should contain "openid"'); + + const authParams = { ...opts.authorizationParams, ...authVerification }; + + if (usePKCE) { + authVerification.code_verifier = client.generateRandomCodeVerifier(); + authParams.code_challenge_method = 'S256'; + authParams.code_challenge = await client.calculateCodeChallenge(authVerification.code_verifier); } - if (authParams.max_age) { - await transientHandler.save('max_age', req, res, { - ...transientOpts, - value: authParams.max_age.toString() - }); + if (responseType !== config.authorizationParams.response_type) { + authVerification.response_type = responseType; } + await transientHandler.save('auth_verification', req, res, { + sameSite: authParams.response_mode === 'form_post' ? 'none' : config.session.cookie.sameSite, + value: JSON.stringify(authVerification) + }); + const authorizationUrl = await client.authorizationUrl(authParams); debug('redirecting to %s', authorizationUrl); diff --git a/src/auth0-session/index.ts b/src/auth0-session/index.ts index d1becc606..4882c4daf 100644 --- a/src/auth0-session/index.ts +++ b/src/auth0-session/index.ts @@ -1,6 +1,7 @@ export { MissingStateParamError, MissingStateCookieError, + MalformedStateCookieError, IdentityProviderError, ApplicationError } from './utils/errors'; diff --git a/src/auth0-session/utils/errors.ts b/src/auth0-session/utils/errors.ts index 62c455a53..96126b69c 100644 --- a/src/auth0-session/utils/errors.ts +++ b/src/auth0-session/utils/errors.ts @@ -22,6 +22,18 @@ export class MissingStateParamError extends Error { } } +export class MalformedStateCookieError extends Error { + static message = 'Your state cookie is not valid JSON.'; + status = 400; + statusCode = 400; + + constructor() { + /* c8 ignore next */ + super(MalformedStateCookieError.message); + Object.setPrototypeOf(this, MalformedStateCookieError.prototype); + } +} + export class MissingStateCookieError extends Error { static message = 'Missing state cookie from login request (check login URL, callback URL and cookie config).'; status = 400; diff --git a/src/shared.ts b/src/shared.ts index c000f207d..a6b6ad742 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -137,6 +137,7 @@ export { export { MissingStateCookieError, + MalformedStateCookieError, MissingStateParamError, IdentityProviderError, ApplicationError diff --git a/tests/auth0-session/handlers/callback.test.ts b/tests/auth0-session/handlers/callback.test.ts index dc685355f..26dcc302c 100644 --- a/tests/auth0-session/handlers/callback.test.ts +++ b/tests/auth0-session/handlers/callback.test.ts @@ -15,6 +15,8 @@ const privateKey = readFileSync(join(__dirname, '..', 'fixtures', 'private-key.p const expectedDefaultState = encodeState({ returnTo: 'https://example.org' }); +const authVerificationCookie = (cookies: Record) => ({ auth_verification: JSON.stringify(cookies) }); + describe('callback', () => { afterEach(teardown); @@ -22,10 +24,10 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ nonce: '__test_nonce__', state: '__test_state__' - }, + }), baseURL ); @@ -50,14 +52,28 @@ describe('callback', () => { ); }); + it('should error when auth_verification cookie is malformed', async () => { + const baseURL = await setup(defaultConfig); + + await expect( + post(baseURL, '/callback', { + body: { + state: '__test_state__', + id_token: '__invalid_token__' + }, + cookieJar: await toSignedCookieJar({ auth_verification: 'not json' }, baseURL) + }) + ).rejects.toThrowError('Your state cookie is not valid JSON.'); + }); + it("should error when state doesn't match", async () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ nonce: '__valid_nonce__', state: '__valid_state__' - }, + }), baseURL ); @@ -76,10 +92,10 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ nonce: '__valid_nonce__', state: '__valid_state__' - }, + }), baseURL ); @@ -98,10 +114,10 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ nonce: '__valid_nonce__', state: '__valid_state__' - }, + }), baseURL ); @@ -122,10 +138,10 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ nonce: '__valid_nonce__', state: '__valid_state__' - }, + }), baseURL ); @@ -144,9 +160,9 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: '__valid_state__' - }, + }), baseURL ); @@ -166,7 +182,7 @@ describe('callback', () => { const cookieJar = await toSignedCookieJar( { - _state: '__valid_state__' + _auth_verification: JSON.stringify({ state: '__valid_state__' }) }, baseURL ); @@ -197,11 +213,11 @@ describe('callback', () => { }; const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__', max_age: '100' - }, + }), baseURL ); @@ -228,10 +244,10 @@ describe('callback', () => { }; const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -254,11 +270,11 @@ describe('callback', () => { const baseURL = await setup({ ...defaultConfig, authorizationParams: { response_type: 'id_token' } }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__', response_type: 'code id_token' - }, + }), baseURL ); @@ -300,10 +316,10 @@ describe('callback', () => { })); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -360,10 +376,10 @@ describe('callback', () => { }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -411,10 +427,10 @@ describe('callback', () => { }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -441,10 +457,10 @@ describe('callback', () => { const state = encodeState({ foo: 'bar' }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: state, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -465,7 +481,7 @@ describe('callback', () => { const redirectUri = 'http://messi:3000/api/auth/callback/runtime'; const baseURL = await setup(defaultConfig, { callbackOptions: { redirectUri } }); const state = encodeState({ foo: 'bar' }); - const cookieJar = await toSignedCookieJar({ state, nonce: '__test_nonce__' }, baseURL); + const cookieJar = await toSignedCookieJar(authVerificationCookie({ state, nonce: '__test_nonce__' }), baseURL); const { res } = await post(baseURL, '/callback', { body: { state: state, @@ -491,10 +507,10 @@ describe('callback', () => { const state = encodeState({ foo: 'bar' }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: state, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -523,10 +539,10 @@ describe('callback', () => { const state = encodeState({ foo: 'bar' }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: state, nonce: '__test_nonce__' - }, + }), baseURL ); @@ -547,11 +563,11 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__', response_type: 'code id_token' - }, + }), baseURL ); @@ -572,11 +588,11 @@ describe('callback', () => { const baseURL = await setup(defaultConfig); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__', response_type: 'code id_token' - }, + }), baseURL ); @@ -599,11 +615,11 @@ describe('callback', () => { nock('https://op2.example.com').get('/.well-known/openid-configuration').reply(500); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: expectedDefaultState, nonce: '__test_nonce__', response_type: 'code id_token' - }, + }), baseURL ); diff --git a/tests/auth0-session/handlers/login.test.ts b/tests/auth0-session/handlers/login.test.ts index 31f7fbd80..8aeda3c94 100644 --- a/tests/auth0-session/handlers/login.test.ts +++ b/tests/auth0-session/handlers/login.test.ts @@ -5,6 +5,8 @@ import { defaultConfig, fromCookieJar, get, getCookie } from '../fixtures/helper import { decodeState, encodeState } from '../../../src/auth0-session/utils/encoding'; import { LoginOptions } from '../../../src/auth0-session'; +const authVerificationCookie = (cookie: string) => JSON.parse(decodeURIComponent(cookie)); + describe('login', () => { afterEach(teardown); @@ -32,9 +34,9 @@ describe('login', () => { }) }); - expect(fromCookieJar(cookieJar, baseURL)).toMatchObject({ - _state: parsed.query.state, - _nonce: parsed.query.nonce + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL)._auth_verification)).toMatchObject({ + state: parsed.query.state, + nonce: parsed.query.nonce }); }); @@ -69,7 +71,7 @@ describe('login', () => { }) }); - expect(fromCookieJar(cookieJar, baseURL)).toMatchObject({ + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL).auth_verification)).toMatchObject({ code_verifier: expect.any(String), state: parsed.query.state, nonce: parsed.query.nonce @@ -107,10 +109,10 @@ describe('login', () => { }) }); - expect(fromCookieJar(cookieJar, baseURL)).toMatchObject({ - _code_verifier: expect.any(String), - _state: parsed.query.state, - _nonce: parsed.query.nonce + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL)._auth_verification)).toMatchObject({ + code_verifier: expect.any(String), + state: parsed.query.state, + nonce: parsed.query.nonce }); }); @@ -120,11 +122,9 @@ describe('login', () => { await get(baseURL, '/login', { fullResponse: true, cookieJar }); - expect(fromCookieJar(cookieJar, baseURL)).toEqual( - expect.objectContaining({ - _max_age: '100' - }) - ); + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL)._auth_verification)).toMatchObject({ + max_age: 100 + }); }); it('should allow custom login returnTo param', async () => { @@ -141,7 +141,9 @@ describe('login', () => { returnTo: '/foo' }); - expect(fromCookieJar(cookieJar, baseURL)._state).toEqual(parsed.query.state); + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL)._auth_verification)).toMatchObject({ + state: parsed.query.state + }); }); it('should not allow removing openid from scope', async () => { @@ -174,7 +176,9 @@ describe('login', () => { ); await get(baseURL, '/login', { cookieJar }); - expect(fromCookieJar(cookieJar, baseURL)._response_type).toEqual('code'); + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL)._auth_verification)).toMatchObject({ + response_type: 'code' + }); }); it('should use a custom state builder', async () => { @@ -200,7 +204,9 @@ describe('login', () => { customProp: '__test_custom_prop__' }); - expect(fromCookieJar(cookieJar, baseURL)._state).toEqual(parsed.query.state); + expect(authVerificationCookie(fromCookieJar(cookieJar, baseURL)._auth_verification)).toMatchObject({ + state: parsed.query.state + }); }); it('should throw on invalid state from custom state builder', async () => { @@ -224,7 +230,7 @@ describe('login', () => { const { res } = await get(baseURL, '/login', { fullResponse: true, cookieJar }); expect(res.statusCode).toEqual(302); - const cookie = getCookie('state', cookieJar, baseURL); + const cookie = getCookie('auth_verification', cookieJar, baseURL); expect(cookie?.sameSite).toEqual('lax'); expect(cookie?.secure).toBeFalsy(); }); @@ -250,7 +256,7 @@ describe('login', () => { const { res } = await get(baseURL, '/login', { fullResponse: true, cookieJar }); expect(res.statusCode).toEqual(302); - const cookie = getCookie('state', cookieJar, baseURL); + const cookie = getCookie('auth_verification', cookieJar, baseURL); expect(cookie?.sameSite).toEqual('none'); expect(cookie?.secure).toBeTruthy(); }); diff --git a/tests/auth0-session/handlers/logout.test.ts b/tests/auth0-session/handlers/logout.test.ts index d55394d80..2afb75209 100644 --- a/tests/auth0-session/handlers/logout.test.ts +++ b/tests/auth0-session/handlers/logout.test.ts @@ -10,7 +10,7 @@ import wellKnown from '../fixtures/well-known.json'; const login = async (baseURL: string): Promise => { const nonce = '__test_nonce__'; const state = encodeState({ returnTo: 'https://example.org' }); - const cookieJar = await toSignedCookieJar({ state, nonce }, baseURL); + const cookieJar = await toSignedCookieJar({ auth_verification: JSON.stringify({ state, nonce }) }, baseURL); await post(baseURL, '/callback', { body: { state, @@ -89,7 +89,7 @@ describe('logout route', () => { }); const nonce = '__test_nonce__'; const state = encodeState({ returnTo: 'https://example.org' }); - const cookieJar = await toSignedCookieJar({ state, nonce }, baseURL); + const cookieJar = await toSignedCookieJar({ auth_verification: JSON.stringify({ state, nonce }) }, baseURL); await post(baseURL, '/callback', { body: { state, diff --git a/tests/auth0-session/session/stateful-session.test.ts b/tests/auth0-session/session/stateful-session.test.ts index f556d1b49..20d27edb1 100644 --- a/tests/auth0-session/session/stateful-session.test.ts +++ b/tests/auth0-session/session/stateful-session.test.ts @@ -14,7 +14,10 @@ const epochNow = (Date.now() / 1000) | 0; const login = async (baseURL: string, existingSession?: { appSession: string }): Promise => { const nonce = '__test_nonce__'; const state = encodeState({ returnTo: 'https://example.org' }); - const cookieJar = await toSignedCookieJar({ state, nonce, ...existingSession }, baseURL); + const cookieJar = await toSignedCookieJar( + { auth_verification: JSON.stringify({ state, nonce }), ...existingSession }, + baseURL + ); await post(baseURL, '/callback', { body: { state, diff --git a/tests/fixtures/app-router-helpers.ts b/tests/fixtures/app-router-helpers.ts index ffa3c29c2..d84eabe25 100644 --- a/tests/fixtures/app-router-helpers.ts +++ b/tests/fixtures/app-router-helpers.ts @@ -126,9 +126,10 @@ export const login = async (opts: LoginOpts = {}) => { url: `/api/auth/callback?state=${state}&code=code`, cookies: { ...opts.cookies, - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') + auth_verification: await signCookie( + 'auth_verification', + JSON.stringify({ state, nonce: '__test_nonce__', code_verifier: '__test_code_verifier__' }) + ) } }); }; diff --git a/tests/fixtures/setup.ts b/tests/fixtures/setup.ts index 1ab24897d..b4144f590 100644 --- a/tests/fixtures/setup.ts +++ b/tests/fixtures/setup.ts @@ -135,7 +135,7 @@ export const teardown = async (): Promise => { export const login = async (baseUrl: string): Promise => { const nonce = '__test_nonce__'; const state = encodeState({ returnTo: '/' }); - const cookieJar = await toSignedCookieJar({ state, nonce }, baseUrl); + const cookieJar = await toSignedCookieJar({ auth_verification: JSON.stringify({ state, nonce }) }, baseUrl); await post(baseUrl, '/api/auth/callback', { fullResponse: true, body: { diff --git a/tests/handlers/callback-page-router.test.ts b/tests/handlers/callback-page-router.test.ts index ccd7ed3d0..dc1b5df2f 100644 --- a/tests/handlers/callback-page-router.test.ts +++ b/tests/handlers/callback-page-router.test.ts @@ -1,15 +1,13 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { CookieJar } from 'tough-cookie'; -import * as jose from 'jose'; import timekeeper from 'timekeeper'; import { withApi, withoutApi } from '../fixtures/default-settings'; import { makeIdToken } from '../auth0-session/fixtures/cert'; -import { defaultConfig, get, post, toSignedCookieJar } from '../auth0-session/fixtures/helpers'; +import { get, post, toSignedCookieJar } from '../auth0-session/fixtures/helpers'; import { encodeState } from '../../src/auth0-session/utils/encoding'; import { defaultOnError, setup, teardown } from '../fixtures/setup'; import { Session, AfterCallbackPageRoute, MissingStateCookieError } from '../../src'; import nock from 'nock'; -import { signing } from '../../src/auth0-session/utils/hkdf'; const callback = (baseUrl: string, body: any, cookieJar?: CookieJar): Promise => post(baseUrl, `/api/auth/callback`, { @@ -18,13 +16,7 @@ const callback = (baseUrl: string, body: any, cookieJar?: CookieJar): Promise => { - const key = await signing(defaultConfig.secret as string); - const { signature } = await new jose.FlattenedSign(new TextEncoder().encode(`${cookie}=${value}`)) - .setProtectedHeader({ alg: 'HS256', b64: false, crit: ['b64'] }) - .sign(key); - return signature; -}; +const authVerificationCookie = (cookies: Record) => ({ auth_verification: JSON.stringify(cookies) }); describe('callback handler (page router)', () => { afterEach(teardown); @@ -49,9 +41,9 @@ describe('callback handler (page router)', () => { test('should validate the state', async () => { const baseUrl = await setup(withoutApi); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state: '__other_state__' - }, + }), baseUrl ); await expect( @@ -69,10 +61,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withoutApi, { idTokenClaims: { aud: 'bar' } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); await expect( @@ -91,10 +83,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withoutApi, { idTokenClaims: { aud: 'bar', iss: 'other-issuer' } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); await expect( @@ -112,9 +104,9 @@ describe('callback handler (page router)', () => { test('should escape html in error qp', async () => { const baseUrl = await setup(withoutApi); const cookieJar = await toSignedCookieJar( - { - state: `foo.${await generateSignature('state', 'foo')}` - }, + authVerificationCookie({ + state: 'foo' + }), baseUrl ); await expect( @@ -126,10 +118,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withoutApi); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const { res } = await callback( @@ -153,10 +145,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withoutApi); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const { res } = await post(baseUrl, `/api/auth/callback`, { @@ -180,10 +172,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withApi); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const { res } = await callback( @@ -226,10 +218,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const { res } = await callback( @@ -265,10 +257,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const { res } = await callback( @@ -299,10 +291,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); await expect( @@ -321,10 +313,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup({ ...withApi, organization: 'org_foo' }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); await expect( @@ -343,10 +335,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup({ ...withApi, organization: 'org_foo' }, { idTokenClaims: { org_id: 'org_bar' } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); await expect( @@ -370,10 +362,10 @@ describe('callback handler (page router)', () => { }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); await expect( @@ -399,10 +391,10 @@ describe('callback handler (page router)', () => { }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const spy = jest.fn(); @@ -442,10 +434,10 @@ describe('callback handler (page router)', () => { const baseUrl = await setup(withApi, { callbackOptions: { afterCallback } }); const state = encodeState({ returnTo: baseUrl }); const cookieJar = await toSignedCookieJar( - { + authVerificationCookie({ state, nonce: '__test_nonce__' - }, + }), baseUrl ); const { res } = await callback( diff --git a/tests/handlers/callback.test.ts b/tests/handlers/callback.test.ts index 3d8983ac6..8e732a49a 100644 --- a/tests/handlers/callback.test.ts +++ b/tests/handlers/callback.test.ts @@ -8,15 +8,19 @@ import { encodeState } from '../../src/auth0-session/utils/encoding'; import type { Session } from '../../src'; import { getResponse, mockFetch, getSession as getSessionFromRes } from '../fixtures/app-router-helpers'; +const authVerificationCookie = async (cookies: Record) => ({ + auth_verification: await signCookie('auth_verification', JSON.stringify(cookies)) +}); + describe('callback handler (app router)', () => { beforeEach(mockFetch); test('should require a state parameter', async () => { const res = await getResponse({ url: '/api/auth/callback', - cookies: { - state: await signCookie('state', 'foo') - } + cookies: await authVerificationCookie({ + state: 'foo' + }) }); expect(res.status).toBe(400); expect(res.statusText).toMatch(/Missing state parameter|response parameter "state" missing/); @@ -33,9 +37,9 @@ describe('callback handler (app router)', () => { test('should validate the state', async () => { const res = await getResponse({ url: '/api/auth/callback?state=__test_state__', - cookies: { - state: await signCookie('state', 'other_state') - } + cookies: await authVerificationCookie({ + state: 'other_state' + }) }); expect(res.status).toBe(400); expect(res.statusText).toMatch( @@ -47,11 +51,11 @@ describe('callback handler (app router)', () => { const state = encodeState({ returnTo: 'https://example.com' }); const res = await getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state: state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), idTokenClaims: { aud: 'bar' } }); expect(res.status).toBe(400); @@ -64,11 +68,11 @@ describe('callback handler (app router)', () => { const state = encodeState({ returnTo: 'https://example.com' }); const res = await getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state: state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), idTokenClaims: { iss: 'other-issuer' } }); expect(res.status).toBe(400); @@ -80,9 +84,9 @@ describe('callback handler (app router)', () => { test('should escape html in error qp', async () => { const res = await getResponse({ url: '/api/auth/callback?error=%3Cscript%3Ealert(%27xss%27)%3C%2Fscript%3E&state=foo', - cookies: { - state: await signCookie('state', 'foo') - } + cookies: await authVerificationCookie({ + state: 'foo' + }) }); expect(res.status).toBe(400); expect(res.statusText).toMatch(/<script>alert\('xss'\)<\/script>/); @@ -92,11 +96,11 @@ describe('callback handler (app router)', () => { const state = encodeState({ returnTo: 'https://example.com/foo' }); const res = await getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - } + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }) }); expect(res.status).toEqual(302); expect(res.headers.get('location')).toEqual('https://example.com/foo'); @@ -112,11 +116,11 @@ describe('callback handler (app router)', () => { const state = encodeState({ returnTo: 'https://example.com/foo' }); const res = await getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { afterCallback(_req: NextRequest, session: Session) { delete session.accessToken; @@ -137,11 +141,11 @@ describe('callback handler (app router)', () => { const state = encodeState({ returnTo: 'https://example.com/foo' }); const res = await getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state: state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { afterCallback(_req: NextRequest, session: Session) { return { ...session, foo: 'bar' }; @@ -162,11 +166,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { afterCallback() { throw new Error('some validation error.'); @@ -180,11 +184,11 @@ describe('callback handler (app router)', () => { const state = encodeState({ returnTo: 'https://example.com/foo' }); const res = await getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { afterCallback() { return NextResponse.redirect('https://example.com/foo'); @@ -200,11 +204,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { organization: 'org_foo' } @@ -220,11 +224,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { organization: 'foo' } @@ -240,11 +244,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { organization: 'org_foo' }, @@ -263,11 +267,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { organization: 'foo' }, @@ -286,11 +290,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { organization: 'org_foo' }, @@ -306,11 +310,11 @@ describe('callback handler (app router)', () => { await expect( getResponse({ url: `/api/auth/callback?state=${state}&code=code`, - cookies: { - state: await signCookie('state', state), - nonce: await signCookie('nonce', '__test_nonce__'), - code_verifier: await signCookie('code_verifier', '__test_code_verifier__') - }, + cookies: await authVerificationCookie({ + state, + nonce: '__test_nonce__', + code_verifier: '__test_code_verifier__' + }), callbackOpts: { organization: 'foo' }, diff --git a/tests/handlers/login-page-router.test.ts b/tests/handlers/login-page-router.test.ts index 340b84ddf..44e19ee1a 100644 --- a/tests/handlers/login-page-router.test.ts +++ b/tests/handlers/login-page-router.test.ts @@ -3,7 +3,7 @@ import { withoutApi, withApi } from '../fixtures/default-settings'; import { decodeState } from '../../src/auth0-session/utils/encoding'; import { setup, teardown } from '../fixtures/setup'; import { get, getCookie } from '../auth0-session/fixtures/helpers'; -import { Cookie, CookieJar } from 'tough-cookie'; +import { CookieJar } from 'tough-cookie'; describe('login handler (page router)', () => { afterEach(teardown); @@ -16,19 +16,7 @@ describe('login handler (page router)', () => { expect(cookieJar.getCookiesSync(baseUrl)).toEqual( expect.arrayContaining([ expect.objectContaining({ - key: 'nonce', - value: expect.any(String), - path: '/', - sameSite: 'lax' - }), - expect.objectContaining({ - key: 'state', - value: expect.any(String), - path: '/', - sameSite: 'lax' - }), - expect.objectContaining({ - key: 'code_verifier', + key: 'auth_verification', value: expect.any(String), path: '/', sameSite: 'lax' @@ -42,10 +30,11 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; expect(state).toBeTruthy(); - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toEqual('/custom-url'); }); @@ -58,7 +47,8 @@ describe('login handler (page router)', () => { expect(statusCode).toBe(302); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; expect(urlParse(headers.location, true)).toMatchObject({ protocol: 'https:', host: 'acme.auth0.local', @@ -69,7 +59,7 @@ describe('login handler (page router)', () => { response_type: 'code', redirect_uri: 'http://www.acme.com/api/auth/callback', nonce: expect.any(String), - state: state.split('.')[0], + state, code_challenge: expect.any(String), code_challenge_method: 'S256' }, @@ -149,9 +139,10 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ foo: 'bar', returnTo: 'http://www.acme.com/' @@ -171,9 +162,10 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ foo: 'bar', returnTo: '/profile' @@ -194,9 +186,10 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ foo: 'bar', returnTo: '/foo' @@ -210,9 +203,10 @@ describe('login handler (page router)', () => { const baseUrl = await setup(withoutApi, { loginOptions }); const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ returnTo: new URL('/foo', withoutApi.baseURL).toString() }); @@ -225,9 +219,10 @@ describe('login handler (page router)', () => { const baseUrl = await setup(withoutApi, { loginOptions }); const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login?returnTo=/foo&returnTo=/bar', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ returnTo: new URL('/foo', withoutApi.baseURL).toString() }); @@ -241,9 +236,10 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({}); }); @@ -255,9 +251,10 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login?returnTo=/foo?url=https://www.google.com', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ returnTo: new URL('/foo?url=https://www.google.com', withoutApi.baseURL).toString() }); @@ -274,9 +271,10 @@ describe('login handler (page router)', () => { const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ returnTo: 'https://other-org.acme.com/foo' }); @@ -294,9 +292,10 @@ describe('login handler (page router)', () => { const baseUrl = await setup(withoutApi, { loginOptions }); const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login', { cookieJar }); - const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie; + const { value: authVerification } = getCookie('auth_verification', cookieJar, baseUrl)!; + const state = JSON.parse(decodeURIComponent(authVerification).split('.')[0]).state; - const decodedState = decodeState(state.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ returnTo: '/foo' }); diff --git a/tests/handlers/login.test.ts b/tests/handlers/login.test.ts index 1a77e6d74..75be303f3 100644 --- a/tests/handlers/login.test.ts +++ b/tests/handlers/login.test.ts @@ -12,17 +12,7 @@ describe('login handler (app router)', () => { const res = await getResponse({ url: '/api/auth/login' }); - expect(res.cookies.get('nonce')).toMatchObject({ - value: expect.any(String), - path: '/', - sameSite: 'lax' - }); - expect(res.cookies.get('state')).toMatchObject({ - value: expect.any(String), - path: '/', - sameSite: 'lax' - }); - expect(res.cookies.get('code_verifier')).toMatchObject({ + expect(res.cookies.get('auth_verification')).toMatchObject({ value: expect.any(String), path: '/', sameSite: 'lax' @@ -34,8 +24,9 @@ describe('login handler (app router)', () => { url: '/api/auth/login', loginOpts: { returnTo: '/custom-url' } }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toEqual('/custom-url'); }); @@ -43,7 +34,8 @@ describe('login handler (app router)', () => { const res = await getResponse({ url: '/api/auth/login' }); - const { value: state } = res.cookies.get('state'); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); expect(urlParse(res.headers.get('location'), true)).toMatchObject({ protocol: 'https:', host: 'acme.auth0.local', @@ -54,7 +46,7 @@ describe('login handler (app router)', () => { response_type: 'code', redirect_uri: 'http://www.acme.com/api/auth/callback', nonce: expect.any(String), - state: state.split('.')[0], + state, code_challenge: expect.any(String), code_challenge_method: 'S256' }, @@ -119,8 +111,9 @@ describe('login handler (app router)', () => { } } }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ foo: 'bar', returnTo: 'http://www.acme.com/' @@ -137,8 +130,9 @@ describe('login handler (app router)', () => { } } }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ foo: 'bar', returnTo: '/profile' @@ -155,8 +149,9 @@ describe('login handler (app router)', () => { } } }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState).toEqual({ foo: 'bar', returnTo: '/bar' @@ -167,8 +162,9 @@ describe('login handler (app router)', () => { const res = await getResponse({ url: '/api/auth/login?returnTo=/from-query' }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toEqual('http://www.acme.com/from-query'); }); @@ -176,8 +172,9 @@ describe('login handler (app router)', () => { const res = await getResponse({ url: '/api/auth/login?returnTo=/foo&returnTo=bar' }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toEqual('http://www.acme.com/foo'); }); @@ -185,8 +182,9 @@ describe('login handler (app router)', () => { const res = await getResponse({ url: '/api/auth/login?returnTo=https://evil.com' }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toBeUndefined(); }); @@ -195,8 +193,9 @@ describe('login handler (app router)', () => { url: '/api/auth/login', loginOpts: { returnTo: 'https://google.com' } }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toBe('https://google.com'); }); @@ -210,8 +209,9 @@ describe('login handler (app router)', () => { url: '/api/auth/login?returnTo=/bar', loginOpts }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toBe('https://other-org.acme.com/bar'); }); @@ -224,8 +224,9 @@ describe('login handler (app router)', () => { } } }); - const { value: state } = res.cookies.get('state'); - const decodedState = decodeState(state.split('.')[0]); + const { value: authVerification } = res.cookies.get('auth_verification'); + const { state } = JSON.parse(authVerification.split('.')[0]); + const decodedState = decodeState(state); expect(decodedState?.returnTo).toBe('/bar'); });