Skip to content

Commit 02bc237

Browse files
fix handleCallback not setting cookie
1 parent efbadb5 commit 02bc237

2 files changed

Lines changed: 63 additions & 23 deletions

File tree

src/server/auth-client.ts

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -413,14 +413,12 @@ export class AuthClient {
413413
}
414414
}
415415

416-
/**
416+
/**
417417
* Proxy handler that routes to the appropriate proxy handler method
418418
* @param nextRequest The incoming NextRequest instance.
419419
* @returns A Promise that resolves to a NextResponse or undefined.
420420
*/
421-
async proxyHandler(
422-
nextRequest: NextRequest,
423-
): Promise<NextResponse | void> {
421+
async proxyHandler(nextRequest: NextRequest): Promise<NextResponse | void> {
424422
// Convert the incomming request to Auth0Request
425423
const auth0Req = new Auth0NextRequest(nextRequest as NextRequest);
426424
// Convert the incoming response to Auth0Response
@@ -485,7 +483,7 @@ export class AuthClient {
485483
nextRequest: NextRequest | NextApiRequest,
486484
fallback?: (req: Auth0Request, res: Auth0Response) => Promise<Auth0Response>,
487485
nextResponse?: NextApiResponse
488-
): Promise<NextResponse | undefined> {
486+
): Promise<NextResponse | void> {
489487
// Convert the incomming request to Auth0Request
490488
const auth0Req = isRequest(nextRequest)
491489
? new Auth0NextRequest(nextRequest as NextRequest)
@@ -554,14 +552,27 @@ export class AuthClient {
554552
}
555553

556554
/**
557-
* Unwraps an Auth0Response by extracting the underlying NextResponse.
555+
* Unwraps an Auth0Response by extracting the underlying NextResponse, or calling `.end()` on the underlying NextApiResponse.
558556
* This utility simplifies the pattern of awaiting handler calls and accessing .res
557+
*
558+
* @param handler A function that returns a Promise resolving to an Auth0Response.
559+
* @returns A Promise that resolves to a NextResponse or void (in case of Pages Router usage).
559560
*/
560561
async #unwrapHandler(
561562
handler: () => Promise<Auth0Response>
562-
): Promise<NextResponse> {
563+
): Promise<NextResponse | void> {
563564
const auth0Response = await handler();
564-
return auth0Response.res;
565+
const response = auth0Response.res;
566+
const canEndRequest =
567+
response && "end" in response && typeof response.end === "function";
568+
569+
// When the underlying response supports .end(), call it to finalize the response without returning the NextApiResponse instance.
570+
// NextResponse does not have .end(), instead we return the NextResponse instance.
571+
if (canEndRequest) {
572+
response.end();
573+
} else {
574+
return response as NextResponse;
575+
}
565576
}
566577

567578
async startInteractiveLogin(
@@ -806,14 +817,25 @@ export class AuthClient {
806817
auth0Req.getCookies(),
807818
state
808819
);
820+
821+
const baseOnCallbackCtx: OnCallbackContext = {
822+
request: auth0Req.req,
823+
response: isRequest(auth0Req.req) ? undefined : auth0Res.res
824+
};
825+
809826
if (!transactionStateCookie) {
810-
const errorRes = await this.onCallback(new InvalidStateError(), {}, null);
827+
const errorRes = await this.onCallback(
828+
new InvalidStateError(),
829+
baseOnCallbackCtx,
830+
null
831+
);
811832
auth0Res.setResponse(errorRes);
812833
return auth0Res;
813834
}
814835

815836
const transactionState = transactionStateCookie.payload;
816837
const onCallbackCtx: OnCallbackContext = {
838+
...baseOnCallbackCtx,
817839
responseType: transactionState.responseType,
818840
returnTo: transactionState.returnTo
819841
};
@@ -881,7 +903,7 @@ export class AuthClient {
881903
session
882904
);
883905

884-
auth0Res.setResponse(res);
906+
auth0Res.setResponse(res ?? onCallbackCtx.response);
885907
await this.transactionStore.delete(auth0Res.getCookies(), state);
886908

887909
return auth0Res;
@@ -1028,7 +1050,7 @@ export class AuthClient {
10281050
// if not then filter id_token claims with default rules
10291051
session = await this.finalizeSession(session, oidcRes.id_token);
10301052

1031-
auth0Res.setResponse(res);
1053+
auth0Res.setResponse(res ?? onCallbackCtx.response);
10321054
await this.sessionStore.set(
10331055
auth0Req.getCookies(),
10341056
auth0Res.getCookies(),
@@ -1537,17 +1559,32 @@ export class AuthClient {
15371559
error: SdkError | null,
15381560
ctx: OnCallbackContext
15391561
) {
1540-
if (error) {
1541-
return new NextResponse(error.message, {
1542-
status: 500
1543-
});
1544-
}
1562+
const redirectUrl = createRouteUrl(ctx.returnTo || "/", this.appBaseUrl);
15451563

1546-
const res = NextResponse.redirect(
1547-
createRouteUrl(ctx.returnTo || "/", this.appBaseUrl)
1548-
);
1564+
if (ctx.request && isRequest(ctx.request)) {
1565+
if (error) {
1566+
return new NextResponse(error.message, {
1567+
status: 500
1568+
});
1569+
}
1570+
1571+
const res = NextResponse.redirect(redirectUrl);
15491572

1550-
return res;
1573+
return res;
1574+
} else {
1575+
if (error) {
1576+
ctx.response?.status(500).send(error.message);
1577+
} else {
1578+
// We do not use ctx.response.redirect(), as that would call `res.end()` immediately.
1579+
// Even though that should be okay conceptually,
1580+
// it would require changing code to not set cookies after calling `onCallback()`,
1581+
// which is not in scope.
1582+
1583+
// This also means that anyone using a custom onCallback should not end the response themselves.
1584+
// If they would, the Set-Cookie headers set after onCallback would be ignored and the transaction cookie will not exist.
1585+
ctx.response?.status(307).setHeader("Location", redirectUrl.toString());
1586+
}
1587+
}
15511588
}
15521589

15531590
/**
@@ -1561,7 +1598,7 @@ export class AuthClient {
15611598
): Promise<Auth0Response> {
15621599
const response = await this.onCallback(error, ctx, null);
15631600

1564-
auth0Res.setResponse(response);
1601+
auth0Res.setResponse(response ?? ctx.response);
15651602

15661603
// Clean up the transaction cookie on error to prevent accumulation
15671604
if (state) {
@@ -2953,4 +2990,4 @@ export async function buildConnectAccountErrorResponse(
29532990
null
29542991
];
29552992
}
2956-
}
2993+
}

src/server/http/auth0-next-api-response.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export class Auth0NextApiResponse extends Auth0Response<NextApiResponse> {
2626
* @param url The URL to redirect to.
2727
*/
2828
redirect(url: string) {
29-
this.res = this.res.redirect(url);
29+
// We do not use .redirect(), as that would call `res.end()` immediately.
30+
// Even though that should be okay conceptually,
31+
// it would require changing code to not set cookies after calling `redirect()`.
32+
this.res = this.res.status(307).setHeader("Location", url);
3033
return this;
3134
}
3235

0 commit comments

Comments
 (0)