Skip to content

WIP: DO NOT MERGE - fix(fxa-settings): passkey registration failures on iOS and Windows Hello#20767

Closed
vpomerleau wants to merge 2 commits into
mainfrom
FXA-13991
Closed

WIP: DO NOT MERGE - fix(fxa-settings): passkey registration failures on iOS and Windows Hello#20767
vpomerleau wants to merge 2 commits into
mainfrom
FXA-13991

Conversation

@vpomerleau

Copy link
Copy Markdown
Contributor

Because

  • On Firefox, creating a passkey fails: on iOS the WebContent renderer crashes and the setup prompt loops; on desktop with Windows Hello it errors out and can't complete. (FXA-13991)
  • Both stem from requesting the WebAuthn PRF extension during registration — iOS WebKit before 26.5.1 crashes serializing the PRF result, and Windows Hello without the PRF platform update rejects the ceremony before any prompt. PRF only powers Phase-2 passwordless Sync, so neither should block a user from creating a passkey.

This pull request

  • Serializes the credential by hand in toCredentialJSON (packages/fxa-settings/src/lib/passkeys/webauthn.ts) using the WebAuthn Level 2 getters instead of the native PublicKeyCredential.toJSON() that crashes the WebKit renderer when a PRF output is present (verified on an iOS 26.2 device).
  • When a registration create() fails in a way the optional PRF probe could have caused, keeps the user on the add page with an error banner and a "Try again" action (packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.tsx) instead of returning to settings; the retry strips the PRF extension and reuses the cached options, producing a working passkey without it.
  • Adds isRetriableWithoutPrf and stripPrfExtension (packages/fxa-settings/src/lib/passkeys/prf-fallback.ts), which reuse the shared WebAuthn error categorizer so any "unexpected" ceremony failure is handled, with unit tests.
  • Records Glean prf_unsupported and prf_retry_failed reasons, and adds a functional test (a new prf-unsupported polyfill mode in packages/functional-tests/lib/passkeyPolyfill.ts) that drives the reject → on-page retry → success flow.

Issue that this pull request solves

Closes: FXA-13991

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.ts (manual L2 serialization), PagePasskeyAdd/index.tsx (on-page retry flow), prf-fallback.ts (retry predicate).
  • Suggested review order: webauthn.tsprf-fallback.tsPagePasskeyAdd/index.tsx → functional test.
  • Risky or complex parts: the manual credential serialization must round-trip byte-exact for server verification; the retry depends on the manual click providing fresh user activation (the consumed-activation reason the auto-retry was dropped).

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.

Because:
 - WebKit before 26.5.1 crashes the WebContent renderer inside its native
   PublicKeyCredential.toJSON() when a prf extension output is present,
   breaking passkey registration on iOS with an uncatchable process trap that
   no try/catch can recover.

This commit:
 - Serializes the credential manually in toCredentialJSON via the long-standing
   Level 2 getters (base64url-encode the response ArrayBuffers; read extension
   results via getClientExtensionResults) instead of native toJSON(), which
   predate and bypass the broken serializer. Verified on an iOS 26.2 device
   for both create and get.
 - Drops the now-unused toJSON global augmentation and hasToJSON guard, and
   moves the webauthn unit tests onto the manual-serialization path.
Because:
 - Requesting the WebAuthn PRF extension at registration makes the ceremony
   fail outright on platform authenticators that can't provision it — notably
   Windows Hello without the PRF platform update, where create() rejects before
   any prompt. PRF only powers Phase-2 passwordless Sync; a passkey without it
   still works for sign-in, so a hard failure blocks registration needlessly.

This commit:
 - When registration fails in a way an optional PRF probe could have caused
   (the create error categorizes as "unexpected"), keeps the user on the add
   page with a "Try again" button instead of bouncing to settings. The click is
   a fresh user activation, so the retry — which strips PRF and reuses the
   cached options/challenge — runs create() immediately. Success records a
   non-PRF passkey; a failed retry returns to settings with the generic
   help-link message.
 - Adds isRetriableWithoutPrf (reuses the shared error categorizer rather than
   matching one error name, so other "unexpected" manifestations are covered
   too) and stripPrfExtension, both unit tested. Surfaces the on-page failure in
   an error Banner with a short message and a retry action.
 - Records Glean reasons prf_unsupported / prf_retry_failed for visibility.
 - Adds a functional test (new prf-unsupported polyfill mode) driving the
   reject -> on-page retry -> success flow, and updates the WebAuthn polyfill to
   expose Level 2 getters (the manual-serialization fix reads them) and to carry
   the DOMException name across the exposeFunction boundary.

Closes #FXA-13991
@vpomerleau

Copy link
Copy Markdown
Contributor Author

Closing this exploratory fix - I've linked it in the originating ticket for future work.

@vpomerleau vpomerleau closed this Jun 23, 2026
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.

1 participant