From c1f9bc588c35e5d358f127d9701a09fc5b95bea8 Mon Sep 17 00:00:00 2001 From: Lucky Rathore Date: Tue, 2 Dec 2025 08:51:04 +0000 Subject: [PATCH 1/2] Fix: Prevent cookies from being set when onSuccess callback throws an error --- src/authkit-callback-route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/authkit-callback-route.ts b/src/authkit-callback-route.ts index daf4e89..dfc0548 100644 --- a/src/authkit-callback-route.ts +++ b/src/authkit-callback-route.ts @@ -99,8 +99,6 @@ export function handleAuth(options: HandleAuthOptions = {}) { if (!accessToken || !refreshToken) throw new Error('response is missing tokens'); - await saveSession({ accessToken, refreshToken, user, impersonator }, request); - if (onSuccess) { await onSuccess({ accessToken, @@ -114,6 +112,8 @@ export function handleAuth(options: HandleAuthOptions = {}) { }); } + await saveSession({ accessToken, refreshToken, user, impersonator }, request); + return response; } catch (error) { const errorRes = { From ac64e92c48a7d960fbfa1e2c7b764436055cc212 Mon Sep 17 00:00:00 2001 From: Lucky Rathore Date: Wed, 3 Dec 2025 09:33:04 +0000 Subject: [PATCH 2/2] fix: update cookie-deletion logic and add tests --- src/authkit-callback-route.spec.ts | 34 +++++++++++++++++++++++++++++- src/authkit-callback-route.ts | 31 +++++++++++++++------------ src/session.ts | 6 ++++++ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/authkit-callback-route.spec.ts b/src/authkit-callback-route.spec.ts index 530079e..5e5ca24 100644 --- a/src/authkit-callback-route.spec.ts +++ b/src/authkit-callback-route.spec.ts @@ -51,7 +51,7 @@ describe('authkit-callback-route', () => { beforeAll(() => { // Silence console.error during tests - jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'error').mockImplementation(() => { }); }); beforeEach(async () => { @@ -344,5 +344,37 @@ describe('authkit-callback-route', () => { // Should still redirect correctly expect(response.headers.get('Location')).toContain('/old-path'); }); + + it('should return error and set no cookies when onSuccess throws', async () => { + // Mock authenticate success + jest.mocked(workos.userManagement.authenticateWithCode).mockResolvedValue(mockAuthResponse); + + // Prepare request with code + state + request.nextUrl.searchParams.set('code', 'test-code'); + request.nextUrl.searchParams.set('state', 'dummy-state'); + + // Make onSuccess throw intentionally + const onSuccess = jest.fn(() => { + throw new Error('onSuccess failed'); + }); + + const handler = handleAuth({ onSuccess }); + + const response = await handler(request); + + // 1. Status must be error (400, 500 — depends on your handler) + expect(response.status).toBeGreaterThanOrEqual(400); + + // 2. Response should contain error message + const body = await response.json(); + expect(body.error).toBeDefined(); + expect(body.error.message).toBe('Something went wrong'); + + // 3. No cookies should be set + // NextResponse stores cookies in headers.getSetCookie() + + const nextCookies = await cookies(); + expect(nextCookies.getAll()).toHaveLength(0); + }); }); }); diff --git a/src/authkit-callback-route.ts b/src/authkit-callback-route.ts index dfc0548..3e7a15c 100644 --- a/src/authkit-callback-route.ts +++ b/src/authkit-callback-route.ts @@ -1,7 +1,7 @@ import { NextRequest } from 'next/server'; import { WORKOS_CLIENT_ID } from './env-variables.js'; import { HandleAuthOptions } from './interfaces.js'; -import { saveSession } from './session.js'; +import { deleteSession, saveSession } from './session.js'; import { errorResponseWithFallback, redirectWithFallback, setCachePreventionHeaders } from './utils.js'; import { getWorkOS } from './workos.js'; @@ -99,21 +99,26 @@ export function handleAuth(options: HandleAuthOptions = {}) { if (!accessToken || !refreshToken) throw new Error('response is missing tokens'); + await saveSession({ accessToken, refreshToken, user, impersonator }, request); + if (onSuccess) { - await onSuccess({ - accessToken, - refreshToken, - user, - impersonator, - oauthTokens, - authenticationMethod, - organizationId, - state: customState, - }); + try { + await onSuccess({ + accessToken, + refreshToken, + user, + impersonator, + oauthTokens, + authenticationMethod, + organizationId, + state: customState, + }); + } catch (error) { + deleteSession(); + throw error; + } } - await saveSession({ accessToken, refreshToken, user, impersonator }, request); - return response; } catch (error) { const errorRes = { diff --git a/src/session.ts b/src/session.ts index 9513891..3966e47 100644 --- a/src/session.ts +++ b/src/session.ts @@ -625,4 +625,10 @@ export async function saveSession( nextCookies.set(cookieName, encryptedSession, getCookieOptions(url)); } +export async function deleteSession() { + const nextCookies = await cookies(); + const cookieName = WORKOS_COOKIE_NAME || 'wos-session'; + nextCookies.delete(cookieName); +} + export { encryptSession, refreshSession, updateSession, updateSessionMiddleware, withAuth };