Skip to content

Fix WebAuthn passkey authenticator selection for browser extension compatibility#161

Closed
shuaiplus wants to merge 1 commit intomainfrom
codex/fix-passkey-authentication-for-nodewarden
Closed

Fix WebAuthn passkey authenticator selection for browser extension compatibility#161
shuaiplus wants to merge 1 commit intomainfrom
codex/fix-passkey-authentication-for-nodewarden

Conversation

@shuaiplus
Copy link
Copy Markdown
Owner

Motivation

  • Make passkey registration/login behave more like Bitwarden official server so browser extensions (external authenticators) are discovered instead of falling back to OS platform passkey prompts.
  • Provide deterministic, server-side transport hints so browsers/extensions can match credentials reliably across browsers and platforms.
  • Avoid any new configurable variables; use a locked set of standard WebAuthn transports.

Description

  • Add a locked transport whitelist WEBAUTHN_TRANSPORTS = ['usb','nfc','ble','hybrid','internal'] and implement normalizeTransports/parseStoredTransports to strictly validate and normalize transports on the server in src/handlers/passkeys.ts.
  • Set authenticatorSelection.authenticatorAttachment to "cross-platform" in the registration payload to favor external/extension authenticators during handleBeginPasskeyRegistration in src/handlers/passkeys.ts.
  • Capture AuthenticatorAttestationResponse.getTransports() from the client and persist credential.response.transports (serialized) when registering a passkey in handleFinishPasskeyRegistration in src/handlers/passkeys.ts.
  • Include transport hints in allowCredentials for login (handleBeginPasskeyLogin) using stored transports or falling back to the locked full transport list so browsers/extensions can better match the right authenticator in src/handlers/passkeys.ts.
  • Update the webapp to return attestation transports by adding transports: typeof response.getTransports === 'function' ? response.getTransports() : [] in webapp/src/lib/passkey.ts so transports flow end-to-end from client to server.

Testing

  • Ran the production webapp build with npm run build, which completed successfully.

Codex Task

Copilot AI review requested due to automatic review settings March 31, 2026 15:01
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 31, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
nodewarden 719d110 Mar 31 2026, 03:02 PM

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 allowCredentials during 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.

Comment on lines 200 to +204
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] };
}),
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
},
pubKeyCredParams: [{ type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 }],
authenticatorSelection: {
authenticatorAttachment: 'cross-platform',
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
authenticatorAttachment: 'cross-platform',

Copilot uses AI. Check for mistakes.
@shuaiplus shuaiplus closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants