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 daf4e89..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'; @@ -102,16 +102,21 @@ export function handleAuth(options: HandleAuthOptions = {}) { 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; + } } return response; 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 };