Skip to content

feat(passkeys): Support passkeys verification method for sessionTokens#20379

Merged
vpomerleau merged 1 commit intomainfrom
FXA-13097
Apr 15, 2026
Merged

feat(passkeys): Support passkeys verification method for sessionTokens#20379
vpomerleau merged 1 commit intomainfrom
FXA-13097

Conversation

@vpomerleau
Copy link
Copy Markdown
Contributor

Because

  • Passkey authentication needs a verified session token with verificationMethod = 5 and AAL2. Adding passkey also required fixing AAL enforcement, which could have blocked password sign-in for passkey-holding accounts (passkeys are optional; only TOTP mandates 2FA).

This pull request

  • Registers passkey as verification method across auth-server, fxa-shared, and account repository
  • Maps passkey -> webauth AMR in authMethods
  • Adds maximumAssuranceLevelRequired() and replaces the old AAL enforcement pattern in the session auth strategies
  • Adds createPasskeySessionToken utility to PasskeyHandler (needed for FXA-13095)
  • Adds PASSKEY/WEBAUTHN enum values to fxa-settings and fxa-content-server
  • Extends unit tests across affected files

Issue that this pull request solves

Closes: FXA-13097

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: This work uncovered some issues with current AMR handling. I recommend reading the notes in FXA-13432 for additional context.

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.

// like TOTP), so the RP can decide whether to prompt for step-up.
// It is NOT the AAL of the current session. Passkeys are excluded from
// this computation — a passkey-only account reports AAL1 even though
// AAL2 is achievable. See FXA-13432.
Copy link
Copy Markdown
Contributor

@dschom dschom Apr 15, 2026

Choose a reason for hiding this comment

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

Just for my own personal clarification... A passkey session is a AAL2 session correct? I'm just getting thrown off by the 'is acheivable' comment...

I guess I also don't understand why a 'passkey only' account is excluded from this calculation. Is it that we are concerned the RP will assume TOTP is required? I'd assume a passkey only account would be require AAL2 just like an account with totp. Maybe this is incorrect though.

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.

Passkey session is AAL2, but the account does not necessarily require AAL2 for all sign-ins unless 2FA/TOTP is also enabled.

* Passkeys are optional: registering one does not force AAL2 on password
* sign-ins.
*/
async maximumAssuranceLevelRequired(db, account) {
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.

I feel like this function name is a little fuzzy. It's essentially checking that AAL2 is required. In theory we could support other levels like AAL3 (geo location validation) which would be the maximum level, but we don't support this yet so AAL2 is the max level.

Can we rename this so the method so it describes what the check is for. e.g. accountRequiresAAL2.


beforeEach(() => {
mockDbInstance = mockDB();
db = { totpToken: jest.fn() };
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.

Thanks for cleaning this up!

// and is factually wrong for all of them. For linked accounts and email OTP
// the error is inconsequential — {pwd, email} → {know, know} → AAL1 whether
// or not pwd is present. For passkey sessions it is load-bearing: without it,
// {webauthn} → {have} → AAL1; with it, {pwd, webauthn} → {know, have} → AAL2.
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.

Is this true? Why doesn't webauthn alone imply AAL2? It feels to me like it indicates you both have a physical device and your finger print, passcode, etc... to access that paskey is very much like a password, ie it's something you know and provide.

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.

The explanation here can be improved. It was intended to explain the limitations of the current mapping, with full fix deferred to FXA-13432. Webauthn alone does provide AAL2.

uid: account.uid,
});
} catch (cleanupErr) {
this.log.error('passkeys.createPasskeySessionToken.deleteOrphan', {
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.

Let's file a metric for this. And add a metric for succesful verification.

Comment thread packages/fxa-auth-server/lib/routes/passkeys.ts
Comment thread packages/fxa-auth-server/lib/routes/passkeys.ts
);
});

it('stamps the token with the passkey verification method', async () => {
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.

See comment about single atomic operation over in passkeys.ts

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.

Filed a follow-up (FXA-13444) to replace with a single procedure and clean this up.

Copy link
Copy Markdown
Contributor

@dschom dschom left a comment

Choose a reason for hiding this comment

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

Looking good. I'll get your feedback on the comment about session token creation being atomic. Otherwise ready for the r+.

Because:

* Passkey authentication needs a verified session token with verificationMethod = 5 and AAL2. Adding passkey also required fixing AAL enforcement, which previously blocked password sign-in for passkey-holding accounts (passkeys are optional; only TOTP mandates 2FA).

This commit:

* Registers passkey as verification method across auth-server, fxa-shared, and account repository
* Maps passkey -> webauth AMR in authMethods
* Adds accountRequiresAal2() and replaces the old AAL enforcement pattern in the session auth strategies
* Adds createPasskeySessionToken utility to PasskeyHandler (needed for FXA-13095)
* Adds PASSKEY/WEBAUTHN enum values to fxa-settings and fxa-content-server
* Extends unit tests across affected files

Closes #FXA-13097
@vpomerleau vpomerleau marked this pull request as ready for review April 15, 2026 21:09
@vpomerleau vpomerleau requested a review from a team as a code owner April 15, 2026 21:09
Copilot AI review requested due to automatic review settings April 15, 2026 21:09
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class passkey support as a session token verification method and updates Authenticator Assurance Level (AAL) logic so that only mandatory factors (TOTP) require AAL2, while passkeys remain optional.

Changes:

  • Register passkey as verificationMethod = 5 across shared DB models, auth-server, settings, content-server, and the shared account repository.
  • Map passkey verification to webauthn AMR and introduce accountRequiresAAL2() for clearer AAL enforcement in session auth strategies and session routes.
  • Add PasskeyHandler.createPasskeySessionToken() helper plus new/updated unit tests for passkey AMR/AAL behavior and updated AAL enforcement semantics.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/fxa-shared/db/models/auth/session-token.ts Adds passkey: 5 to the shared verification method mapping used by DB models/utilities.
packages/fxa-settings/src/constants/verification-methods.ts Exposes PASSKEY as a settings verification method constant.
packages/fxa-settings/src/constants/authentication-methods.ts Adds WEBAUTHN authentication method constant for UI/settings usage.
packages/fxa-content-server/app/scripts/lib/verification-methods.ts Exposes PASSKEY constant to the content server.
packages/fxa-auth-server/lib/tokens/session_token.js Adds method value mapping for passkey and documents session-vs-account AAL semantics.
packages/fxa-auth-server/lib/tokens/session_token.spec.ts Adds coverage ensuring passkey-verified sessions yield {pwd, webauthn} and AAL2.
packages/fxa-auth-server/lib/authMethods.js Maps passkey→webauthn AMR, hardens AAL computation, and adds accountRequiresAAL2() for enforcement paths.
packages/fxa-auth-server/lib/authMethods.spec.ts Adds tests for passkey AMR mapping, AAL computation, and accountRequiresAAL2().
packages/fxa-auth-server/lib/routes/session.js Switches AAL enforcement from “account AAL” to accountRequiresAAL2() (TOTP-only requirement).
packages/fxa-auth-server/lib/routes/session.spec.ts Updates expectations to match the new “requires AAL2?” semantics in /session/status.
packages/fxa-auth-server/lib/routes/auth-schemes/verified-session-token.js Updates enforcement to require AAL2 only when the account requires it (TOTP enabled).
packages/fxa-auth-server/lib/routes/auth-schemes/verified-session-token.spec.ts Adds/adjusts tests for the new AAL enforcement behavior.
packages/fxa-auth-server/lib/routes/auth-schemes/mfa.ts Updates MFA auth scheme to use accountRequiresAAL2() for enforcement.
packages/fxa-auth-server/lib/routes/auth-schemes/mfa.spec.ts Updates tests to stub TOTP state via db.totpToken rather than AMR set.
packages/fxa-auth-server/lib/routes/passkeys.ts Adds PasskeyHandler.createPasskeySessionToken() and wires statsd into handler construction.
packages/fxa-auth-server/lib/routes/passkeys.spec.ts Adds focused unit tests for createPasskeySessionToken() behavior and cleanup on partial failure.
packages/fxa-auth-server/lib/routes/account.ts Clarifies that profile:amr AAL is account-level (mandatory factors) and excludes passkeys.
libs/shared/account/account/src/lib/account.repository.ts Adds passkey = 5 to the shared account repository verification methods enum.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@vpomerleau vpomerleau merged commit ce475bb into main Apr 15, 2026
26 checks passed
@vpomerleau vpomerleau deleted the FXA-13097 branch April 15, 2026 22:27
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