feat(passkeys): Support passkeys verification method for sessionTokens#20379
feat(passkeys): Support passkeys verification method for sessionTokens#20379vpomerleau merged 1 commit intomainfrom
Conversation
| // 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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() }; |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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', { |
There was a problem hiding this comment.
Let's file a metric for this. And add a metric for succesful verification.
| ); | ||
| }); | ||
|
|
||
| it('stamps the token with the passkey verification method', async () => { |
There was a problem hiding this comment.
See comment about single atomic operation over in passkeys.ts
There was a problem hiding this comment.
Filed a follow-up (FXA-13444) to replace with a single procedure and clean this up.
dschom
left a comment
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
passkeyasverificationMethod = 5across shared DB models, auth-server, settings, content-server, and the shared account repository. - Map
passkeyverification towebauthnAMR and introduceaccountRequiresAAL2()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.
Because
This pull request
Issue that this pull request solves
Closes: FXA-13097
Checklist
Put an
xin the boxes that applyHow to review (Optional)
FXA-13432for 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.