Repository: auth0/nextjs-auth0
Pull request #1343 (2023-08-03 16:43:02)

Move state cookies to under a single cookie
### 📋 Changes

Currently we set up to 5 cookies to verify the authentication response (state, nonce, max_age, code_verifier, response_type)

Am moving these under a single cookie to make it easier to customize the cookie name and settings to implement a solution for #1297

The same change has been done in express-openid-connect https://github.com/auth0/express-openid-connect/pull/168

The change is to 2 files:
- src/auth0-session/handlers/callback.ts
- src/auth0-session/handlers/login.ts
 
(the rest of the changes are tests)


Patch:
diff --git a/src/auth0-session/handlers/callback.ts b/src/auth0-session/handlers/callback.ts
index 441d679e..efa4c85b 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 fb5700f4..c8ea37ac 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<void>;
 
+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<StoreOptions, 'sameSite'> = {
-      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 d1becc60..4882c4da 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 62c455a5..96126b69 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 c000f207..a6b6ad74 100644
--- a/src/shared.ts
+++ b/src/shared.ts
@@ -137,6 +137,7 @@ export {
 
 export {
   MissingStateCookieError,
+  MalformedStateCookieError,
   MissingStateParamError,
   IdentityProviderError,
   ApplicationError


Test patch:
diff --git a/tests/auth0-session/handlers/callback.test.ts b/tests/auth0-session/handlers/callback.test.ts
index dc685355..26dcc302 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<string, string>) => ({ 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 31f7fbd8..8aeda3c9 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 d55394d8..2afb7520 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<CookieJar> => {
   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 f556d1b4..20d27edb 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<CookieJar> => {
   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 ffa3c29c..d84eabe2 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 1ab24897..b4144f59 100644
--- a/tests/fixtures/setup.ts
+++ b/tests/fixtures/setup.ts
@@ -135,7 +135,7 @@ export const teardown = async (): Promise<void> => {
 export const login = async (baseUrl: string): Promise<CookieJar> => {
   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 ccd7ed3d..dc1b5df2 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<any> =>
   post(baseUrl, `/api/auth/callback`, {
@@ -18,13 +16,7 @@ const callback = (baseUrl: string, body: any, cookieJar?: CookieJar): Promise<an
     fullResponse: true
   });
 
-const generateSignature = async (cookie: string, value: string): Promise<string> => {
-  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<string, string>) => ({ 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 3d8983ac..8e732a49 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<string, string>) => ({
+  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 &quot;state&quot; 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(/&lt;script&gt;alert\(&#39;xss&#39;\)&lt;\/script&gt;/);
@@ -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 340b84dd..44e19ee1 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 1a77e6d7..75be303f 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');
   });