diff --git a/EXAMPLES.md b/EXAMPLES.md index 1f5d662b9..16cac44e7 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -2197,11 +2197,12 @@ Do realize that this has an impact on the size of the cookie being issued, so it The `onCallback` hook is run once the user has been redirected back from Auth0 to your application with either an error or the authorization code which will be verified and exchanged. -The `onCallback` hook receives 3 parameters: +The `onCallback` hook receives 4 parameters: 1. `error`: the error returned from Auth0 or when attempting to complete the transaction. This will be `null` if the transaction was completed successfully. 2. `context`: provides context on the transaction that initiated the transaction. 3. `session`: the `SessionData` that will be persisted once the transaction completes successfully. This will be `null` if there was an error. +4. `defaultOnCallback`: the default onCallback hook that would have been called instead of the user-provided hook. This can be used if you want to take an action on callback, but keep the default callback behaviour instead of overriding it. The hook must return a Promise that resolves to a `NextResponse`. diff --git a/V4_MIGRATION_GUIDE.md b/V4_MIGRATION_GUIDE.md index 0c0f0e63d..967e42f0e 100644 --- a/V4_MIGRATION_GUIDE.md +++ b/V4_MIGRATION_GUIDE.md @@ -354,7 +354,7 @@ import { NextResponse } from 'next/server'; import { Auth0Client } from '@auth0/nextjs-auth0/server'; export const auth0 = new Auth0Client({ - async onCallback(error, context, session) { + async onCallback(error, context, session, defaultOnCallback) { if (error) { console.error('Authentication error:', error); return NextResponse.redirect( diff --git a/src/server/auth-client.test.ts b/src/server/auth-client.test.ts index 3caa4b39b..97f029f73 100644 --- a/src/server/auth-client.test.ts +++ b/src/server/auth-client.test.ts @@ -3993,7 +3993,8 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(mockOnCallback).toHaveBeenCalledWith( null, expectedContext, - expectedSession + expectedSession, + expect.any(Function) ); // validate the session cookie @@ -4058,7 +4059,8 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(mockOnCallback).toHaveBeenCalledWith( expect.any(Error), {}, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual("missing_state"); @@ -4137,7 +4139,8 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(mockOnCallback).toHaveBeenCalledWith( expect.any(Error), {}, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual("invalid_state"); @@ -4219,7 +4222,8 @@ ca/T0LLtgmbMmxSv/MmzIg== responseType: RESPONSE_TYPES.CODE, returnTo: transactionState.returnTo }, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual( "authorization_error" @@ -4305,7 +4309,8 @@ ca/T0LLtgmbMmxSv/MmzIg== responseType: RESPONSE_TYPES.CODE, returnTo: transactionState.returnTo }, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual( "authorization_code_grant_request_error" @@ -4390,7 +4395,8 @@ ca/T0LLtgmbMmxSv/MmzIg== responseType: RESPONSE_TYPES.CODE, returnTo: transactionState.returnTo }, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual( "authorization_code_grant_error" @@ -4866,7 +4872,8 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(mockOnCallback).toHaveBeenCalledWith( null, expectedContext, - expectedSession + expectedSession, + expect.any(Function) ); }); @@ -4940,7 +4947,8 @@ ca/T0LLtgmbMmxSv/MmzIg== responseType: RESPONSE_TYPES.CONNECT_CODE, returnTo: transactionState.returnTo }, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual( ConnectAccountErrorCodes.MISSING_SESSION @@ -5055,7 +5063,8 @@ ca/T0LLtgmbMmxSv/MmzIg== responseType: RESPONSE_TYPES.CONNECT_CODE, returnTo: transactionState.returnTo }, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual( AccessTokenErrorCode.MISSING_REFRESH_TOKEN @@ -5173,7 +5182,8 @@ ca/T0LLtgmbMmxSv/MmzIg== responseType: RESPONSE_TYPES.CONNECT_CODE, returnTo: transactionState.returnTo }, - null + null, + expect.any(Function) ); expect(mockOnCallback.mock.calls[0][0].code).toEqual( ConnectAccountErrorCodes.FAILED_TO_COMPLETE diff --git a/src/server/auth-client.ts b/src/server/auth-client.ts index 726155239..0943998c3 100644 --- a/src/server/auth-client.ts +++ b/src/server/auth-client.ts @@ -118,7 +118,11 @@ export type OnCallbackContext = { export type OnCallbackHook = ( error: SdkError | null, ctx: OnCallbackContext, - session: SessionData | null + session: SessionData | null, + defaultOnCallback: ( + error: SdkError | null, + ctx: OnCallbackContext + ) => Promise ) => Promise; // params passed to the /authorize endpoint that cannot be overwritten @@ -676,7 +680,12 @@ export class AuthClient { state ); if (!transactionStateCookie) { - return this.onCallback(new InvalidStateError(), {}, null); + return this.onCallback( + new InvalidStateError(), + {}, + null, + this.defaultOnCallback + ); } const transactionState = transactionStateCookie.payload; @@ -745,7 +754,8 @@ export class AuthClient { ...onCallbackCtx, connectedAccount }, - session + session, + this.defaultOnCallback ); await this.transactionStore.delete(res.cookies, state); @@ -888,7 +898,12 @@ export class AuthClient { } }; - const res = await this.onCallback(null, onCallbackCtx, session); + const res = await this.onCallback( + null, + onCallbackCtx, + session, + this.defaultOnCallback + ); // call beforeSessionSaved callback if present // if not then filter id_token claims with default rules @@ -1413,7 +1428,12 @@ export class AuthClient { req: NextRequest, state?: string ): Promise { - const response = await this.onCallback(error, ctx, null); + const response = await this.onCallback( + error, + ctx, + null, + this.defaultOnCallback + ); // Clean up the transaction cookie on error to prevent accumulation if (state) {