feat(passkeys): implement sign-in ceremony on Index and Signin pages#20575
Conversation
878ef7a to
20b8131
Compare
There was a problem hiding this comment.
Pull request overview
Implements the passkey WebAuthn sign-in ceremony, wiring a new usePasskeySignIn hook into the Index and Signin pages and fixing an auth-server bug in the existing passkey route.
Changes:
- Adds
usePasskeySignInhook owning the WebAuthn ceremony, error categorization (incl. newAbortErrormapping andPASSKEY_NOT_FOUNDbanner), Sync merge gate, account persistence, and post-auth navigation; PII-sanitized Sentry captures. - Wires hook into Index (with
disableAutoSubmitto suppress suggested-email auto-submit on passkey click) and Signin; widensIndexIntegrationand mocks accordingly; mounts/post_verify/passkey/set_password/*for the no-password Sync fallback; hides the button for Sync and cached signin. - Fixes auth-server passkey route to return
sessionToken.data(Hawk key) instead of.id; enablespasskeyAuthenticationEnabledin devlocal.json-dist; adds polyfillassertion()mode and functional tests covering happy/cancel/PASSKEY_NOT_FOUNDpaths.
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-settings/src/lib/passkeys/signin-flow.ts | New hook implementing the full passkey sign-in ceremony |
| packages/fxa-settings/src/lib/passkeys/signin-flow.test.tsx | Unit tests for the hook |
| packages/fxa-settings/src/lib/passkeys/webauthn-errors.ts | Adds AbortError mapping (treated like NotAllowedError) |
| packages/fxa-settings/src/lib/passkeys/webauthn-errors.test.ts | Test coverage for the new AbortError mapping |
| packages/fxa-settings/src/lib/passkeys/en.ftl | New passkey-authentication-error-not-found string |
| packages/fxa-settings/src/pages/Signin/index.tsx | Wires hook; hides button on Sync and cached signin |
| packages/fxa-settings/src/pages/Index/index.tsx | Wires hook; cancels auto-submit before passkey click |
| packages/fxa-settings/src/pages/Index/container.tsx | Plumbs authClient/finishOAuthFlowHandler/disableAutoSubmit |
| packages/fxa-settings/src/pages/Index/interfaces.ts | Widens IndexIntegration with new method picks |
| packages/fxa-settings/src/pages/Index/mocks.tsx | Mock updates for widened integration & new props |
| packages/fxa-settings/src/components/App/index.tsx | Mounts SetPasswordContainer at /post_verify/passkey/set_password/* |
| packages/fxa-content-server/server/config/local.json-dist | Enables passkeyAuthenticationEnabled in dev |
| packages/fxa-auth-server/lib/routes/passkeys.ts | Returns sessionToken.data instead of .id |
| packages/fxa-auth-server/lib/routes/passkeys.spec.ts | Updates assertions for the .data/.id shape |
| packages/functional-tests/lib/passkeyPolyfill.ts | New assertion() mode; passthrough of canonical WebAuthn error names |
| packages/functional-tests/pages/signin.ts | New passkeySigninButton locator; tightens signInButton matcher |
| packages/functional-tests/tests/passkeyAuth/passkey-signin.spec.ts | New functional tests (happy/cancel/PASSKEY_NOT_FOUND) |
| packages/functional-tests/tests/_reference/passkey-virtual-authenticator.spec.ts | Header comment refresh; suite still skipped |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ec2879f to
8f3e9c6
Compare
LZoog
left a comment
There was a problem hiding this comment.
✅ Tested this on both 1Password, and Apple keychain, and can sign in from email-first and on the non-cached signin screen.
- Nice to have: are we planning to add a timestamp to "last used" or just keep that as the date? (I know I could check Figma heheh)
- Are we planning to file an a11y review? Curious what they would recommend - when the button is dimmed, it might make sense for it to tell the user somehow that we're waiting for them to perform an action...
✅ Saw these errors as expected. Do we want to remove the space above? (I get why we have the error messaging here, but it does also read slightly funny with the "Or", maybe we can direct focus there when we do a11y improvements)
❔:
This is probably intentional, but why not hide the button the same way we do third party auth for Sync, is it so we don't have to think about it in phase 2 when some can be used for passkeys, since that'll be a much easier sign-in for those users? Seems reasonable.

Buuut maybe at least until phase 2, we hide the button on this page for Sync users, since all it does is add friction and take them to the exact same page (to enter a password)?
LZoog
left a comment
There was a problem hiding this comment.
@vpomerleau I probably didn't do as thorough of a code review as I could have, but I did look through it and didn't notice anything I'd require a change on before merge, and I did a decent amount of manual testing here. 👍
| * to call the profile server. Five minutes is well over any single request's | ||
| * latency budget while keeping the token's blast radius small. | ||
| */ | ||
| export const PROFILE_OAUTH_TOKEN_TTL_SECONDS = 300; |
There was a problem hiding this comment.
Probably the right call to make this a new file, we should move some stuff in the Constants file over here.
I had some questions about 300, but I see you've just moved it into a new file here, so seems fine.
Because: We need to wire the passkey WebAuthn ceremony to the passkey button introduced in FXA-13487. This commit: * Adds a usePasskeySignIn hook driving the ceremony, error handling, Sync merge gate, and post-auth routing, and wires it into the Index and Signin pages. * Enables passkeyAuthenticationEnabled in local.json-dist. * Adds functional tests covering the passkey sign-in happy, cancel, and PASSKEY_NOT_FOUND paths. * Tweaks SigninPasskeyFallback to align with the latest design (avatar + email row with the real avatar fetched, single full-width Continue button, copy fixes). Closes #FXA-13099
|
Thanks very much @LZoog for the testing and review!
|
Because
This pull request
usePasskeySignInhook owning the WebAuthn ceremony, error categorisation, Sync merge gate, account persistence, and post-auth routing. Validates theaccountProfileresponse before relying onemail, and sanitises Sentry captures so server-error responses cannot leak PII.authClientandfinishOAuthFlowHandlerthrough the Index container./signin/passkey/fallbackas a temporary stand-in for FXA-13100; hides the passkey button on Sync entry-points behind the same TODO.PASSKEY_NOT_FOUND(errno 224); does not report to Sentry since it is an expected divergence between server and authenticator state.sessionToken.datainstead of.idso the client can Hawk-sign subsequent requests.passkeyAuthenticationEnabledinlocal.json-dist(dev only; production flag stays off via the convict default).PASSKEY_NOT_FOUNDpaths.Issue that this pull request solves
Closes: FXA-13099
Checklist
Put an
xin the boxes that applyHow to review
packages/fxa-settings/src/lib/passkeys/signin-flow.ts(the hook),packages/fxa-settings/src/pages/{Index,Signin}/(wiring),packages/fxa-auth-server/lib/routes/passkeys.ts(sessionToken fix),packages/functional-tests/tests/passkeyAuth/passkey-signin.spec.ts(new tests).Other information
!integration.isSync(). FXA-13100 will replace the temporarySetPasswordContainermount at/signin/passkey/fallbackwith a container that derives Sync keys viaverifyPasswordAfterPasskey; the Sync button can re-enable then.sessionToken.id → .datachange inpasskeys.tsis a real bug latent in main since5a17be0bb7— every passkey sign-in via the existing route would have failed downstream Hawk-authenticated calls. Worth filing a separate bug to track for backport visibility.