Skip to content

feat(passkey): Add verification proof to the challenge manager - #21114

Closed
nshirley wants to merge 1 commit into
mainfrom
FXA-13142-1
Closed

feat(passkey): Add verification proof to the challenge manager#21114
nshirley wants to merge 1 commit into
mainfrom
FXA-13142-1

Conversation

@nshirley

Copy link
Copy Markdown
Contributor

Because:

  • A wrap write needs proof that a user-verified assertion just completed for a specific credential, which a session token alone cannot attest.
  • A proof is the same primitive as a challenge — a random value minted, then consumed once — so a second Redis module would be duplication.

This commit:

  • Adds a 'proof' challenge type, separately typed so an upgrade challenge handed out before any assertion cannot be spent as a proof.
  • Adds generateProofChallenge and consumeProofChallenge, credential-scoped.
  • Takes the proof TTL from PASSKEYS__VERIFICATION_PROOF_TIMEOUT, which spans the password entry a ceremony challenge does not.
  • Adds errnos 236 and 237 with their AppError factories.

Closes: Part of FXA-13142 (sister pr is the bulk of that work)

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.

How to review (Optional)

  • Key files/areas to focus on:
  • Suggested review order:
  • Risky or complex parts:

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

Any other information that is important to this pull request.

Comment thread libs/accounts/passkey/src/lib/passkey.challenge.manager.in.spec.ts Outdated
return false;
}

if (stored.credentialId !== credentialId) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should be REALLY unlikely to happen, but defense-in-depth!

Comment thread libs/accounts/errors/src/app-error.ts Outdated
@nshirley
nshirley force-pushed the FXA-13142-1 branch 3 times, most recently from 30b6aca to fdcf48f Compare August 27, 2026 15:58
// Matches the config default (passkeys.challengeTimeout = 300,000 ms)
const CHALLENGE_TIMEOUT_MS = 1000 * 60 * 5;

// Different from CHALLENGE_TIEMOUT_MS to allow tests to deterministically default behavior of proofs

@nshirley nshirley Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
// Different from CHALLENGE_TIEMOUT_MS to allow tests to deterministically default behavior of proofs
// Different from CHALLENGE_TIMEOUT_MS so tests are able to deterministically verify which timeout is used

@nshirley nshirley changed the title task(passkey): Add verification proof to the challenge manager feat(passkey): Add verification proof to the challenge manager Aug 27, 2026
Comment thread libs/accounts/passkey/src/lib/passkey.challenge.manager.ts Outdated

/**
* Length in base64url characters of a challenge or proof token, which are 32
* random bytes. Used to validate a proof on the wire before hitting Redis.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
* random bytes. Used to validate a proof on the wire before hitting Redis.
* random bytes. Used for Joi validation of the proof before hitting Redis.

Comment thread libs/accounts/passkey/src/lib/passkey.challenge.manager.ts Outdated
Because:
* A wrap write needs proof that a user-verified assertion just completed for a
  specific credential, which a session token alone cannot attest.
* A proof is the same primitive as a challenge — a random value minted, then
  consumed once — so a second Redis module would be duplication.

This commit:
* Adds a 'proof' challenge type, separately typed so an upgrade challenge handed
  out before any assertion cannot be spent as a proof.
* Adds generateProofChallenge and consumeProofChallenge, credential-scoped.
* Takes the proof TTL from PASSKEYS__VERIFICATION_PROOF_TIMEOUT, which spans the
  password entry a ceremony challenge does not.
* Adds errno 236, PASSKEY_VERIFICATION_PROOF_INVALID, and its AppError factory.

@vpomerleau vpomerleau 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.

This looks straightforward, just some naming nits. 🎉

* vice versa. UID is required when generating an upgrade challenge.
*
* - 'proof': Not a ceremony challenge. Minted *after* an assertion verifies and
* consumed once to authorise key creation for that credential. Carries no key

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.

Nit: Is it too specific to note this is specifically for key/wrap creation? Conceivably we could use this proof to authorize other actions in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's a good idea, but I don't think we would want to use this for that at this time. The challenges (create, proof, any of them) don't carry any additional authorization material like scopes, so anything that mints a proof could then use that same proof if we accept it on multiple endpoints.

That said, I think we could explore allowing passkeys as an option to mint the existing MFA JWT tokens. But that would likely need a lot more thought, UX, design, etc since we'd have to handle opt-in, fall backs, etc

* - 'proof': Not a ceremony challenge. Minted *after* an assertion verifies and
* consumed once to authorise key creation for that credential. Carries no key
* material; it attests only that the assertion happened. Typed separately from
* 'upgrade' because an upgrade challenge is issued before any assertion — were

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.

Nit: We currently are not using the upgrade challenge, and I wonder if we should rename more generically to authorization challenge? This would conceivably be the challenge we need whenever an already authenticated user uses the passkey to authorize account changes (such as storing a new key wrap or as alternative to the email code MFA).

@nshirley
nshirley marked this pull request as ready for review August 27, 2026 23:30
@nshirley
nshirley requested a review from a team as a code owner August 27, 2026 23:30
Copilot AI balanced review requested due to automatic review settings August 27, 2026 23:30

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

Extends the passkey challenge manager with credential-scoped, single-use verification proofs for subsequent key-wrap creation.

Changes:

  • Adds proof generation and consumption with independent TTL configuration.
  • Adds proof validation errors and errno 236.
  • Adds unit and integration coverage for proof scope, expiry, and single use.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/fxa-auth-server/config/index.ts Adds proof timeout configuration.
libs/accounts/passkey/src/lib/webauthn-adapter.spec.ts Updates test configuration.
libs/accounts/passkey/src/lib/passkey.service.spec.ts Updates service test configuration.
libs/accounts/passkey/src/lib/passkey.provider.ts Extends raw passkey configuration.
libs/accounts/passkey/src/lib/passkey.provider.spec.ts Updates provider fixtures.
libs/accounts/passkey/src/lib/passkey.manager.spec.ts Updates manager fixtures.
libs/accounts/passkey/src/lib/passkey.manager.in.spec.ts Updates integration fixtures.
libs/accounts/passkey/src/lib/passkey.config.ts Models the proof timeout.
libs/accounts/passkey/src/lib/passkey.challenge.manager.ts Implements credential-scoped proofs.
libs/accounts/passkey/src/lib/passkey.challenge.manager.spec.ts Tests proof storage and TTL selection.
libs/accounts/passkey/src/lib/passkey.challenge.manager.in.spec.ts Tests proof isolation and single use.
libs/accounts/errors/src/index.spec.ts Tests the new AppError factory.
libs/accounts/errors/src/constants.ts Adds proof-invalid errno 236.
libs/accounts/errors/src/app-error.ts Adds the proof-invalid AppError factory.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +97 to +98
@IsNumber()
public verificationProofTimeout!: number;
Comment thread libs/accounts/errors/src/constants.ts
@nshirley

Copy link
Copy Markdown
Contributor Author

After further conversation, we've decide to not implement this at this time. If we need it down the road we can always re-open or reference this pr

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