diff --git a/README.md b/README.md index 52eaa15..18ba702 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ export const GET = handleAUth({ | `returnPathname` | `/` | The pathname to redirect the user to after signing in | | `baseURL` | `undefined` | The base URL to use for the redirect URI instead of the one in the request. Useful if the app is being run in a container like docker where the hostname can be different from the one in the request | | `onSuccess` | `undefined` | A function that receives successful authentication data and can be used for side-effects like persisting tokens | +| `onError` | `undefined` | A function that can receive the error and the request and handle the error in its own way. | ### Middleware diff --git a/__tests__/authkit-callback-route.spec.ts b/__tests__/authkit-callback-route.spec.ts index 20a9d3f..958928d 100644 --- a/__tests__/authkit-callback-route.spec.ts +++ b/__tests__/authkit-callback-route.spec.ts @@ -107,6 +107,26 @@ describe('authkit-callback-route', () => { expect(data.error.message).toBe('Something went wrong'); }); + it('should handle authentication failure with custom onError handler', async () => { + // Mock authentication failure + jest.mocked(workos.userManagement.authenticateWithCode).mockRejectedValue('Auth failed'); + request.nextUrl.searchParams.set('code', 'invalid-code'); + + const handler = handleAuth({ + onError: ({ error }) => { + return new Response(JSON.stringify({ error: { message: 'Custom error' } }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + }, + }); + const response = await handler(request); + + expect(response.status).toBe(500); + const data = await response.json(); + expect(data.error.message).toBe('Custom error'); + }); + it('should handle missing code parameter', async () => { const handler = handleAuth(); const response = await handler(request); diff --git a/src/authkit-callback-route.ts b/src/authkit-callback-route.ts index d6d2c3c..33c42b6 100644 --- a/src/authkit-callback-route.ts +++ b/src/authkit-callback-route.ts @@ -1,14 +1,14 @@ -import { NextRequest } from 'next/server'; import { cookies } from 'next/headers'; -import { workos } from './workos.js'; +import { NextRequest } from 'next/server'; +import { getCookieOptions } from './cookie.js'; import { WORKOS_CLIENT_ID, WORKOS_COOKIE_NAME } from './env-variables.js'; +import { HandleAuthOptions } from './interfaces.js'; import { encryptSession } from './session.js'; import { errorResponseWithFallback, redirectWithFallback } from './utils.js'; -import { getCookieOptions } from './cookie.js'; -import { HandleAuthOptions } from './interfaces.js'; +import { workos } from './workos.js'; export function handleAuth(options: HandleAuthOptions = {}) { - const { returnPathname: returnPathnameOption = '/', baseURL, onSuccess } = options; + const { returnPathname: returnPathnameOption = '/', baseURL, onSuccess, onError } = options; // Throw early if baseURL is provided but invalid if (baseURL) { @@ -83,14 +83,18 @@ export function handleAuth(options: HandleAuthOptions = {}) { console.error(errorRes); - return errorResponse(); + return errorResponse(request, error); } } - return errorResponse(); + return errorResponse(request); }; - function errorResponse() { + function errorResponse(request: NextRequest, error?: unknown) { + if (onError) { + return onError({ error, request }); + } + return errorResponseWithFallback({ error: { message: 'Something went wrong', diff --git a/src/interfaces.ts b/src/interfaces.ts index 2d5568f..644101d 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,9 +1,11 @@ import { OauthTokens, User } from '@workos-inc/node'; +import { type NextRequest } from 'next/server'; export interface HandleAuthOptions { returnPathname?: string; baseURL?: string; onSuccess?: (data: HandleAuthSuccessData) => void | Promise; + onError?: (params: { error?: unknown; request: NextRequest }) => Response | Promise; } export interface HandleAuthSuccessData extends Session {