Skip to content

test(oauth): cover the PKCE guards on code redemption - #21035

Open
vbudhram wants to merge 1 commit into
mainfrom
fxa-14316
Open

test(oauth): cover the PKCE guards on code redemption#21035
vbudhram wants to merge 1 commit into
mainfrom
fxa-14316

Conversation

@vbudhram

@vbudhram vbudhram commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Because

  • The pairing security review turns on two claims with no regression test behind them: FxA never sees the code_verifier Firefox generates, and a code cannot be redeemed without it.
  • Nothing proved the first claim, and the handler's PKCE gate had no tests at all: mismatchCodeChallenge and missingPkceParameters appeared nowhere in the auth-server suite. The /oauth/token xor that Firefox's redemption depends on had none either.

This pull request

  • Adds three tests in token.spec.ts for the /oauth/token authorization_code xor, the guard a public client cannot sidestep with a client_secret.
  • Adds three tests driving the handler's PKCE gate directly: the happy path, a redemption with no verifier, and a verifier that does not hash to the stored challenge.
  • Adds an integration test in oauth_tests.in.spec.ts that refuses to redeem a real code without the verifier.
  • Adds a functional test in codeVerifierIsolation.spec.ts driving a real Firefox over Marionette, asserting the verifier reaches neither the params handed to content nor any WebChannel reply.
  • Adds a test in firefox.test.ts pinning buildSyncOAuthSearch to its allowlist, so no extra web-channel field reaches /authorization.
  • Hoists the /oauth/token payload validation helper so both describes share it.

Tests only. No production code changed.

Issue that this pull request solves

Closes: https://mozilla-hub.atlassian.net/browse/FXA-14316

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

Other information

Both ACs are covered. AC 2 is covered at the validation, handler, and integration layers. AC 1 is covered by codeVerifierIsolation.spec.ts, which drives a real Firefox over Marionette rather than a mock: it reads the verifier in chrome context, then asserts the value appears in neither the params beginOAuthFlow() hands back nor any WebChannel envelope that page script can observe. firefox.test.ts covers the FxA half, that FxA cannot forward a verifier it is handed.

Mutation-verified against this diff:

  • Removing .xor('client_secret', 'code_verifier') fails both xor negatives.
  • Disabling the if (codeObj.codeChallenge) gate fails both gate negatives, while the happy path keeps passing.
  • Replacing the buildSyncOAuthSearch allowlist with a spread fails the settings test, which then sees code_verifier, sessionToken and unexpected on the wire.
  • Injecting a verifier at WebChannel envelope level fails the functional test.

Deliberate scope limits.

  • Two PKCE gate branches are uncovered: a stored method other than S256, and a verifier for a challenge-less code. Neither can arrive over HTTP, because /authorization pins the method to S256 and writes a challenge if and only if the client is public.
  • token.js:355 calls crypto.timingSafeEqual directly, so an unequal-length stored codeChallenge throws RangeError rather than returning INCORRECT_CODE_CHALLENGE. It fails closed and issues no token. Swapping in the length-safe bufferEqualsConstantTime is a production change, so it stays out of this tests-only PR and is tracked separately.

How to test:

nx test-unit fxa-auth-server
nx test-unit fxa-settings
yarn start infrastructure && nx test-integration fxa-auth-server
# needs the local stack running
cd packages/functional-tests && npx playwright test tests/pairing/codeVerifierIsolation.spec.ts --project=local

@vbudhram
vbudhram marked this pull request as ready for review August 14, 2026 14:25
@vbudhram
vbudhram requested a review from a team as a code owner August 14, 2026 14:25
Copilot AI balanced review requested due to automatic review settings August 14, 2026 14:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds PKCE regression coverage across OAuth validation, token redemption, Firefox parameter forwarding, and WebChannel isolation.

Changes:

  • Tests /oauth/token credential XOR and PKCE rejection paths.
  • Adds end-to-end authorization-code redemption coverage.
  • Verifies Firefox does not expose or forward the PKCE verifier.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
firefox.test.ts Tests OAuth parameter allowlisting.
oauth_tests.in.spec.ts Tests redemption without a verifier.
token.spec.ts Tests XOR validation and PKCE guards.
codeVerifierIsolation.spec.ts Tests verifier isolation in Firefox/WebChannel.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +222 to +224
for (const message of replies) {
expect(JSON.stringify(message)).not.toContain(storedVerifier);
}
Comment on lines +1915 to +1920
it('rejects a verifier that does not hash to the stored challenge', async () => {
await expect(
redeem(CHALLENGED_CODE, {
code_verifier: 'w'.repeat(PKCE_CODE_VERIFIER.length),
})
).rejects.toMatchObject({ errno: OAUTH_ERRNO.INCORRECT_CODE_CHALLENGE });
Comment on lines +128 to +130
test('Firefox keeps the code_verifier out of the params it hands to content', async ({
marionetteAuthority,
}) => {
Comment thread packages/functional-tests/tests/pairing/codeVerifierIsolation.spec.ts Dismissed
Because:

- The pairing security review turns on two claims: FxA never sees the
  code_verifier Firefox generates, and a code cannot be redeemed without it.
- Nothing proved the first claim, and the handler PKCE gate had no tests at all.
  The /oauth/token xor that Firefox's redemption depends on had none either.

This commit:

- Adds six tests for the /oauth/token guards: the authorization_code xor a
  public client cannot sidestep with a client_secret, and the handler PKCE gate.
- Adds an integration test that refuses to redeem a real code without the
  verifier, sending grant_type explicitly as Firefox does.
- Adds a functional test driving real Firefox over Marionette, proving the
  verifier reaches neither the params Firefox hands to content nor page script.
- Adds a test pinning buildSyncOAuthSearch to its allowlist, so no extra
  web-channel field can reach /authorization.
- Hoists the /oauth/token payload validation helper so both describes share it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants