Skip to content

Commit 82acfed

Browse files
fix: handle authorization code grant request errors (#2175)
Co-authored-by: Frederik Prijck <frederik.prijck@auth0.com>
1 parent ecebf0c commit 82acfed

File tree

3 files changed

+121
-14
lines changed

3 files changed

+121
-14
lines changed

src/errors/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ export class AuthorizationError extends SdkError {
5757
}
5858
}
5959

60+
export class AuthorizationCodeGrantRequestError extends SdkError {
61+
public code: string = "authorization_code_grant_request_error";
62+
63+
constructor(message?: string) {
64+
super(
65+
message ??
66+
"An error occured while preparing or performing the authorization code grant request."
67+
);
68+
this.name = "AuthorizationCodeGrantRequestError";
69+
}
70+
}
71+
6072
export class AuthorizationCodeGrantError extends SdkError {
6173
public code: string = "authorization_code_grant_error";
6274
public cause: OAuth2Error;

src/server/auth-client.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ ca/T0LLtgmbMmxSv/MmzIg==
5858
function getMockAuthorizationServer({
5959
tokenEndpointResponse,
6060
tokenEndpointErrorResponse,
61+
tokenEndpointFetchError,
6162
discoveryResponse,
6263
audience,
6364
nonce,
@@ -66,6 +67,7 @@ ca/T0LLtgmbMmxSv/MmzIg==
6667
}: {
6768
tokenEndpointResponse?: oauth.TokenEndpointResponse | oauth.OAuth2Error;
6869
tokenEndpointErrorResponse?: oauth.OAuth2Error;
70+
tokenEndpointFetchError?: Error;
6971
discoveryResponse?: Response;
7072
audience?: string;
7173
nonce?: string;
@@ -86,6 +88,10 @@ ca/T0LLtgmbMmxSv/MmzIg==
8688
}
8789

8890
if (url.pathname === "/oauth/token") {
91+
if (tokenEndpointFetchError) {
92+
throw tokenEndpointFetchError;
93+
}
94+
8995
const jwt = await new jose.SignJWT({
9096
sid: DEFAULT.sid,
9197
auth_time: Date.now(),
@@ -3305,6 +3311,85 @@ ca/T0LLtgmbMmxSv/MmzIg==
33053311
expect(sessionCookie).toBeUndefined();
33063312
});
33073313

3314+
it("should be called with an error when there is an error performing the authorization code grant request", async () => {
3315+
const state = "transaction-state";
3316+
const code = "auth-code";
3317+
3318+
const mockOnCallback = vi
3319+
.fn()
3320+
.mockResolvedValue(
3321+
NextResponse.redirect(new URL("/error-page", DEFAULT.appBaseUrl))
3322+
);
3323+
3324+
const secret = await generateSecret(32);
3325+
const transactionStore = new TransactionStore({
3326+
secret
3327+
});
3328+
const sessionStore = new StatelessSessionStore({
3329+
secret
3330+
});
3331+
const authClient = new AuthClient({
3332+
transactionStore,
3333+
sessionStore,
3334+
3335+
domain: DEFAULT.domain,
3336+
clientId: DEFAULT.clientId,
3337+
clientSecret: DEFAULT.clientSecret,
3338+
3339+
secret,
3340+
appBaseUrl: DEFAULT.appBaseUrl,
3341+
3342+
fetch: getMockAuthorizationServer({
3343+
tokenEndpointFetchError: new Error("Timeout error")
3344+
}),
3345+
3346+
onCallback: mockOnCallback
3347+
});
3348+
3349+
const url = new URL("/auth/callback", DEFAULT.appBaseUrl);
3350+
url.searchParams.set("code", code);
3351+
url.searchParams.set("state", state);
3352+
3353+
const headers = new Headers();
3354+
const transactionState: TransactionState = {
3355+
nonce: "nonce-value",
3356+
maxAge: 3600,
3357+
codeVerifier: "code-verifier",
3358+
responseType: "code",
3359+
state: state,
3360+
returnTo: "/dashboard"
3361+
};
3362+
const maxAge = 60 * 60; // 1 hour
3363+
const expiration = Math.floor(Date.now() / 1000 + maxAge);
3364+
headers.set(
3365+
"cookie",
3366+
`__txn_${state}=${await encrypt(transactionState, secret, expiration)}`
3367+
);
3368+
const request = new NextRequest(url, {
3369+
method: "GET",
3370+
headers
3371+
});
3372+
3373+
// validate the new response redirect
3374+
const response = await authClient.handleCallback(request);
3375+
expect(response.status).toEqual(307);
3376+
expect(response.headers.get("Location")).not.toBeNull();
3377+
3378+
const redirectUrl = new URL(response.headers.get("Location")!);
3379+
expect(redirectUrl.pathname).toEqual("/error-page");
3380+
3381+
expect(mockOnCallback).toHaveBeenCalledWith(
3382+
expect.any(Error),
3383+
{
3384+
returnTo: transactionState.returnTo
3385+
},
3386+
null
3387+
);
3388+
expect(mockOnCallback.mock.calls[0][0].code).toEqual(
3389+
"authorization_code_grant_request_error"
3390+
);
3391+
});
3392+
33083393
it("should be called with an error if there was an error during the code exchange", async () => {
33093394
const state = "transaction-state";
33103395
const code = "auth-code";

src/server/auth-client.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
AccessTokenForConnectionError,
1010
AccessTokenForConnectionErrorCode,
1111
AuthorizationCodeGrantError,
12+
AuthorizationCodeGrantRequestError,
1213
AuthorizationError,
1314
BackchannelLogoutError,
1415
DiscoveryError,
@@ -502,20 +503,29 @@ export class AuthClient {
502503
);
503504
}
504505

505-
const redirectUri = createRouteUrl(this.routes.callback, this.appBaseUrl); // must be registed with the authorization server
506-
const codeGrantResponse = await oauth.authorizationCodeGrantRequest(
507-
authorizationServerMetadata,
508-
this.clientMetadata,
509-
await this.getClientAuth(),
510-
codeGrantParams,
511-
redirectUri.toString(),
512-
transactionState.codeVerifier,
513-
{
514-
...this.httpOptions(),
515-
[oauth.customFetch]: this.fetch,
516-
[oauth.allowInsecureRequests]: this.allowInsecureRequests
517-
}
518-
);
506+
let codeGrantResponse: Response;
507+
try {
508+
const redirectUri = createRouteUrl(this.routes.callback, this.appBaseUrl); // must be registed with the authorization server
509+
codeGrantResponse = await oauth.authorizationCodeGrantRequest(
510+
authorizationServerMetadata,
511+
this.clientMetadata,
512+
await this.getClientAuth(),
513+
codeGrantParams,
514+
redirectUri.toString(),
515+
transactionState.codeVerifier,
516+
{
517+
...this.httpOptions(),
518+
[oauth.customFetch]: this.fetch,
519+
[oauth.allowInsecureRequests]: this.allowInsecureRequests
520+
}
521+
);
522+
} catch (e: any) {
523+
return this.onCallback(
524+
new AuthorizationCodeGrantRequestError(e.message),
525+
onCallbackCtx,
526+
null
527+
);
528+
}
519529

520530
let oidcRes: oauth.TokenEndpointResponse;
521531
try {

0 commit comments

Comments
 (0)