Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/authkit-callback-route.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
});
});
});
27 changes: 16 additions & 11 deletions src/authkit-callback-route.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };