Skip to content

fix(passkeys): handle authenticator user-verification failures#20821

Draft
vpomerleau wants to merge 1 commit into
mainfrom
FXA-14080
Draft

fix(passkeys): handle authenticator user-verification failures#20821
vpomerleau wants to merge 1 commit into
mainfrom
FXA-14080

Conversation

@vpomerleau

@vpomerleau vpomerleau commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Because

  • Since the Phase-1 passkey rollout, passkey registration has been failing in production (Sentry FXA-AUTH-39Z): when an authenticator completes the ceremony without performing user verification (UV=0) — e.g. a roaming security key with no PIN — SimpleWebAuthn rejects it because the server requires UV for the AAL2 invariant. This is expected client/authenticator behaviour, but it surfaced as an HTTP 500 and was logged as Sentry noise.
  • Affected users saw a generic "Passkey registration failed" with no guidance on how to succeed, and a stalled device prompt showed only a spinner with no explanation.

This pull request

  • Classifies the user-verification failure in libs/accounts/passkey/src/lib/webauthn-adapter.ts via a typed UserVerificationRequiredError, so the passkey service returns a 422 (errno 233, PASSKEY_USER_VERIFICATION_REQUIRED) instead of a 500 and skips Sentry capture for this expected case, while keeping a passkey.registration.failed{reason=userVerificationFailed} metric.
  • Applies the same classification to the authentication path in libs/accounts/passkey/src/lib/passkey.service.ts (metric only; keeps the existing 401 with no distinct errno, to avoid leaking credential capability on sign-in).
  • Adds errno 233 to libs/accounts/errors/src/{constants.ts,app-error.ts} and registers it on the client (packages/fxa-settings/src/lib/auth-errors/) with a device-agnostic message guiding users to add a screen lock, PIN, or biometric, or choose another method.
  • Adds a non-blocking, device-agnostic info Banner (role="status"/aria-live) under the "Creating passkey…" spinner once the ceremony stalls, in packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.tsx, plus a Storybook case.
  • Keeps user verification strictly required (no change to the AAL2 invariant); adds unit and component tests.

Issue that this pull request solves

Closes: FXA-14080

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: webauthn-adapter.ts (the isUserVerificationError classification + both verify paths), passkey.service.ts (catch-block branching), and PagePasskeyAdd/index.tsx (the timed hint Banner).
  • Suggested review order: errors lib (constants.tsapp-error.ts) → passkey adapter → passkey service → client error registration (auth-errors.ts + en.ftl) → PagePasskeyAdd.
  • Risky or complex parts: the UV classification matches SimpleWebAuthn's error message (the adapter specs exercise the real library, so a wording change fails CI); confirm the 422/errno-233 propagates to the client (the route rethrows and Hapi serializes it).

Screenshots (Optional)

Stalled-ceremony info banner ("Taking a while? …")
image

Other information (Optional)

  • User verification remains strictly required — this change only improves error handling and guidance, it does not relax the AAL2 invariant.
  • Introduces a new client-visible error contract: errno 233 / HTTP 422 (PASSKEY_USER_VERIFICATION_REQUIRED).
  • Relates to an anecdotal report of the browser prompt appearing to hang when using a security key with no PIN configured; the stalled-ceremony hint addresses that surface.

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

Improves passkey UX and operational noise by classifying WebAuthn “user verification not performed” failures (UV=0) as an expected, user-actionable error, returning a specific client-visible errno (233) for registration, and surfacing clearer guidance in the Settings UI when registration stalls.

Changes:

  • Server: Introduces UserVerificationRequiredError in the passkey adapter and maps registration UV failures to AppError.passkeyUserVerificationRequired() (HTTP 422 / errno 233), while tagging metrics and avoiding Sentry capture for this expected case.
  • Client: Registers errno 233 for localized display and adds a timed, non-blocking “Taking a while?” Banner under the passkey creation spinner (plus tests/stories).
  • Tests: Adds adapter/service unit coverage for UV classification + client component coverage for the delayed hint and errno-233 UX.

Reviewed changes

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

Show a summary per file
File Description
packages/fxa-settings/src/lib/auth-errors/en.ftl Adds localized string for auth-error-233.
packages/fxa-settings/src/lib/auth-errors/auth-errors.ts Registers PASSKEY_USER_VERIFICATION_REQUIRED mapping to errno 233 for UI.
packages/fxa-settings/src/lib/auth-errors/auth-errors.test.ts Verifies getErrorFtlId resolves auth-error-233.
packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.tsx Adds delayed informational Banner when passkey ceremony appears stalled.
packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.test.tsx Tests delayed hint behavior and errno-233 handling without Sentry capture.
packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.stories.tsx Adds Storybook scenario to showcase stalled ceremony hint (0ms delay).
packages/fxa-settings/src/components/Settings/PagePasskeyAdd/en.ftl Adds localized copy for the stalled-ceremony hint banner.
libs/accounts/passkey/src/lib/webauthn-adapter.ts Classifies SimpleWebAuthn UV failures into a typed UserVerificationRequiredError.
libs/accounts/passkey/src/lib/webauthn-adapter.spec.ts Ensures UV failures are classified and non-UV failures are not misclassified.
libs/accounts/passkey/src/lib/passkey.service.ts Maps registration UV failures to 422/233 (no Sentry), tags auth failures in metrics.
libs/accounts/passkey/src/lib/passkey.service.spec.ts Adds coverage for Sentry capture suppression + metric tagging on UV failure.
libs/accounts/errors/src/index.spec.ts Adds test for AppError.passkeyUserVerificationRequired().
libs/accounts/errors/src/constants.ts Adds ERRNO.PASSKEY_USER_VERIFICATION_REQUIRED = 233.
libs/accounts/errors/src/app-error.ts Adds AppError.passkeyUserVerificationRequired() (422 / errno 233).

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

Comment thread libs/accounts/passkey/src/lib/webauthn-adapter.ts Outdated
Comment thread libs/accounts/passkey/src/lib/webauthn-adapter.ts Outdated
Comment thread libs/accounts/passkey/src/lib/passkey.service.ts Outdated
Because:
* Passkey registration threw a 500 and was captured in Sentry (FXA-AUTH-39Z)
  whenever an authenticator completed the ceremony without user verification
  (UV=0) — e.g. a roaming security key with no PIN — while the server requires
  UV for the AAL2 invariant. This is expected client/authenticator behaviour,
  not a server fault.
* Users saw a generic "Passkey registration failed" with no guidance, and a
  stalled device prompt showed only a spinner.

This commit:
* Classifies the UV failure in the passkey adapter (UserVerificationRequiredError)
  and returns a 422 (errno PASSKEY_USER_VERIFICATION_REQUIRED = 233) instead of a
  500, without capturing it in Sentry; keeps a
  passkey.registration.failed{reason=userVerificationFailed} metric for sizing.
* Applies the same classification to the authentication path (metric only; keeps
  the existing 401, no distinct errno to avoid leaking credential capability).
* Registers errno 233 on the client with a device-agnostic message guiding users
  to add a screen lock, PIN, or biometric, or choose another method.
* Adds a non-blocking, device-agnostic info Banner (role=status/aria-live) under
  the "Creating passkey…" spinner once the ceremony stalls, plus a Storybook case.
* Keeps user verification strictly required; adds unit and component tests.

closes FXA-14080
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.

2 participants