Fix WebAuthn passkey authenticator selection for browser extension compatibility#161
Fix WebAuthn passkey authenticator selection for browser extension compatibility#161
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
nodewarden | 719d110 | Mar 31 2026, 03:02 PM |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 719d110f84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }, | ||
| pubKeyCredParams: [{ type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 }], | ||
| authenticatorSelection: { | ||
| authenticatorAttachment: 'cross-platform', |
There was a problem hiding this comment.
Avoid forcing cross-platform authenticator attachment
Setting authenticatorSelection.authenticatorAttachment to "cross-platform" makes roaming/external authenticators a hard requirement, so users who only have platform passkeys (e.g., Touch ID, Windows Hello, iCloud/Google password manager) can no longer complete registration. This is a behavior regression from the previous unconstrained flow and will break passkey enrollment on many standard setups unless an external key/extension is present.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR adjusts WebAuthn passkey registration/login option shaping to improve compatibility with browser extensions/external authenticators by persisting and reusing transport hints, and by biasing registration toward cross-platform authenticators.
Changes:
- Added server-side transport whitelisting/normalization and persisted attestation transports for each stored passkey.
- Included transport hints in
allowCredentialsduring login initiation to improve credential matching. - Updated the webapp to send
AuthenticatorAttestationResponse.getTransports()results to the server during registration.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| webapp/src/lib/passkey.ts | Adds transport extraction from attestation response so transports can be sent to the backend. |
| src/handlers/passkeys.ts | Normalizes/stores transports, includes transports in login allowCredentials, and sets registration authenticator selection toward cross-platform. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| userVerification: 'preferred', | ||
| allowCredentials: passkeys.map((pk) => ({ type: 'public-key', id: pk.credentialId })), | ||
| allowCredentials: passkeys.map((pk) => { | ||
| const transports = parseStoredTransports(pk.transports); | ||
| return { type: 'public-key', id: pk.credentialId, transports: transports.length ? transports : [...WEBAUTHN_TRANSPORTS] }; | ||
| }), |
There was a problem hiding this comment.
allowCredentials is always included. When email is omitted/unknown (or the user has no stored passkeys), this becomes an empty array, which prevents usernameless/discoverable-credential sign-in even though registration requires residentKey: 'required'. Consider omitting allowCredentials entirely when there are no credential IDs (e.g., set it to undefined / conditionally spread the property) so authenticators can perform resident credential discovery.
| }, | ||
| pubKeyCredParams: [{ type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 }], | ||
| authenticatorSelection: { | ||
| authenticatorAttachment: 'cross-platform', |
There was a problem hiding this comment.
Forcing authenticatorSelection.authenticatorAttachment to 'cross-platform' will block platform authenticators (e.g., built-in OS passkeys) on devices that don't have an external authenticator available, which is a significant behavioral change for registration. If the intent is only to prefer extensions/external authenticators, consider leaving authenticatorAttachment unset (or making it conditional) so platform passkeys remain usable.
| authenticatorAttachment: 'cross-platform', |
Motivation
Description
WEBAUTHN_TRANSPORTS = ['usb','nfc','ble','hybrid','internal']and implementnormalizeTransports/parseStoredTransportsto strictly validate and normalize transports on the server insrc/handlers/passkeys.ts.authenticatorSelection.authenticatorAttachmentto"cross-platform"in the registration payload to favor external/extension authenticators duringhandleBeginPasskeyRegistrationinsrc/handlers/passkeys.ts.AuthenticatorAttestationResponse.getTransports()from the client and persistcredential.response.transports(serialized) when registering a passkey inhandleFinishPasskeyRegistrationinsrc/handlers/passkeys.ts.allowCredentialsfor login (handleBeginPasskeyLogin) using stored transports or falling back to the locked full transport list so browsers/extensions can better match the right authenticator insrc/handlers/passkeys.ts.transports: typeof response.getTransports === 'function' ? response.getTransports() : []inwebapp/src/lib/passkey.tsso transports flow end-to-end from client to server.Testing
npm run build, which completed successfully.Codex Task