Skip to content

Commit 09a2adb

Browse files
authored
Merge pull request #27 from cloudflare/prevent-pkce-downgrade-attack
Mitigated PKCE Downgrade Attack
2 parents 4393dd4 + d954473 commit 09a2adb

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

__tests__/oauth-provider.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,44 @@ describe('OAuthProvider', () => {
773773
expect(error.error_description).toBe('redirect_uri is required when not using PKCE');
774774
});
775775

776+
it('should reject token exchange with code_verifier when PKCE was not used in authorization', async () => {
777+
// First get an auth code WITHOUT using PKCE
778+
const authRequest = createMockRequest(
779+
`https://example.com/authorize?response_type=code&client_id=${clientId}` +
780+
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
781+
`&scope=read%20write&state=xyz123`
782+
);
783+
784+
const authResponse = await oauthProvider.fetch(authRequest, mockEnv, mockCtx);
785+
const location = authResponse.headers.get('Location')!;
786+
const url = new URL(location);
787+
const code = url.searchParams.get('code')!;
788+
789+
// Now exchange the code and incorrectly provide a code_verifier
790+
const params = new URLSearchParams();
791+
params.append('grant_type', 'authorization_code');
792+
params.append('code', code);
793+
params.append('redirect_uri', redirectUri);
794+
params.append('client_id', clientId);
795+
params.append('client_secret', clientSecret);
796+
params.append('code_verifier', 'some_random_verifier_that_wasnt_used_in_auth');
797+
798+
const tokenRequest = createMockRequest(
799+
'https://example.com/oauth/token',
800+
'POST',
801+
{ 'Content-Type': 'application/x-www-form-urlencoded' },
802+
params.toString()
803+
);
804+
805+
const tokenResponse = await oauthProvider.fetch(tokenRequest, mockEnv, mockCtx);
806+
807+
// Should fail because code_verifier is provided but PKCE wasn't used in authorization
808+
expect(tokenResponse.status).toBe(400);
809+
const error = await tokenResponse.json();
810+
expect(error.error).toBe('invalid_request');
811+
expect(error.error_description).toBe('code_verifier provided for a flow that did not use PKCE');
812+
});
813+
776814
// Helper function for PKCE tests
777815
function generateRandomString(length: number): string {
778816
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

src/oauth-provider.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,11 @@ class OAuthProviderImpl {
12371237
return this.createErrorResponse('invalid_grant', 'Invalid redirect URI');
12381238
}
12391239

1240+
// Reject if code_verifier is provided but PKCE wasn't used in authorization
1241+
if (!isPkceEnabled && codeVerifier) {
1242+
return this.createErrorResponse('invalid_request', 'code_verifier provided for a flow that did not use PKCE');
1243+
}
1244+
12401245
// Verify PKCE code_verifier if code_challenge was provided during authorization
12411246
if (isPkceEnabled) {
12421247
if (!codeVerifier) {

0 commit comments

Comments
 (0)