mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-18 11:32:15 -05:00
fix(oauth-provider): return 400 invalid_grant on PKCE verifier mismatch (#11724)
Co-authored-by: Gustavo Valverde <g.valverde02@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@better-auth/oauth-provider": patch
|
||||
---
|
||||
|
||||
Clients exchanging an authorization code with PKCE now get the token errors RFC 7636 and RFC 6749 specify. A `code_verifier` that does not match the authorization request's `code_challenge` returns HTTP 400 `invalid_grant`; it previously returned HTTP 401 `invalid_request`. For clients where PKCE is optional, a missing or unexpected `code_verifier` still returns `invalid_request`, now with HTTP 400 instead of 401. No request that failed before now succeeds.
|
||||
@@ -627,6 +627,9 @@ describe("PKCE optional - offline_access scope", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/11723
|
||||
*/
|
||||
describe("PKCE optional - consistency checks", async () => {
|
||||
const authServerBaseUrl = "http://localhost:3003";
|
||||
const rpBaseUrl = "https://rp.example.com";
|
||||
@@ -713,11 +716,14 @@ describe("PKCE optional - consistency checks", async () => {
|
||||
body,
|
||||
headers,
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(401);
|
||||
expect(context.response.status).toBe(400);
|
||||
},
|
||||
});
|
||||
|
||||
expect(tokenResponse.error).toBeDefined();
|
||||
expect((tokenResponse.error as { error?: string }).error).toBe(
|
||||
"invalid_request",
|
||||
);
|
||||
expect((tokenResponse.error as any).error_description).toContain(
|
||||
"code_verifier required because PKCE was used in authorization",
|
||||
);
|
||||
@@ -761,16 +767,22 @@ describe("PKCE optional - consistency checks", async () => {
|
||||
body,
|
||||
headers,
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(401);
|
||||
expect(context.response.status).toBe(400);
|
||||
},
|
||||
});
|
||||
|
||||
expect(tokenResponse.error).toBeDefined();
|
||||
expect((tokenResponse.error as { error?: string }).error).toBe(
|
||||
"invalid_request",
|
||||
);
|
||||
expect((tokenResponse.error as any).error_description).toContain(
|
||||
"code_verifier provided but PKCE was not used in authorization",
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
|
||||
*/
|
||||
it("mismatched PKCE challenge should fail", async () => {
|
||||
// Authorize with PKCE
|
||||
const codeVerifier = generateRandomString(64);
|
||||
@@ -819,12 +831,15 @@ describe("PKCE optional - consistency checks", async () => {
|
||||
body,
|
||||
headers,
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(401);
|
||||
expect(context.response.status).toBe(400);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(tokenResponse.error).toBeDefined();
|
||||
expect((tokenResponse.error as { error?: string }).error).toBe(
|
||||
"invalid_grant",
|
||||
);
|
||||
expect((tokenResponse.error as any).error_description).toContain(
|
||||
"code verification failed",
|
||||
);
|
||||
|
||||
@@ -1603,7 +1603,7 @@ async function handleAuthorizationCodeGrant(
|
||||
|
||||
if (pkceUsedInAuth && !pkceUsedInToken) {
|
||||
// PKCE was used in authorization but not in token exchange
|
||||
throw new APIError("UNAUTHORIZED", {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
error_description:
|
||||
"code_verifier required because PKCE was used in authorization",
|
||||
error: "invalid_request",
|
||||
@@ -1612,7 +1612,7 @@ async function handleAuthorizationCodeGrant(
|
||||
|
||||
if (!pkceUsedInAuth && pkceUsedInToken) {
|
||||
// PKCE was not used in authorization but verifier provided
|
||||
throw new APIError("UNAUTHORIZED", {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
error_description:
|
||||
"code_verifier provided but PKCE was not used in authorization",
|
||||
error: "invalid_request",
|
||||
@@ -1626,9 +1626,9 @@ async function handleAuthorizationCodeGrant(
|
||||
: undefined;
|
||||
|
||||
if (challenge !== verificationValue.query?.code_challenge) {
|
||||
throw new APIError("UNAUTHORIZED", {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
error_description: "code verification failed",
|
||||
error: "invalid_request",
|
||||
error: "invalid_grant",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user