Skip to content

Add passkeys and hardware security keys as two-factor methods - #1107

Open
nimish-ks wants to merge 3 commits into
mainfrom
feat--passkey-2fa
Open

nimish-ks wants to merge 3 commits into
mainfrom
feat--passkey-2fa

Conversation

@nimish-ks

@nimish-ks nimish-ks commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Passkeys (WebAuthn) as a second factor, alongside or instead of TOTP.

What users get

  • Add a hardware security key (YubiKey, Titan, …), a device passkey (Touch ID, Windows Hello) or a password-manager passkey (Bitwarden, 1Password, iCloud Keychain, Google Password Manager). After the browser prompt, a review step shows the detected provider and an editable name.
  • One shared set of recovery codes: issued with the first factor, deleted with the last.
  • Sign-in offers every method the account has (passkey, authenticator app, recovery code) on a redesigned second-factor screen. /login/mfa with nothing pending redirects to /login.
  • Passkey details (stored metadata as JSON), removal, and method tabs for every "confirm it's you" proof; TOTP setup is now code step → proof step.

Security

  • Every factor change needs proof of a current factor on top of the fresh-session gate; one proof per request.
  • Wrong codes lock codes after 10 attempts (counted before checking); passkeys can't be guessed, so they stay usable while codes are locked.
  • Signature counters enforced for device-bound keys; challenges single-use, per purpose and user.
  • Attestation is requested only so browsers reveal the key model for labels; the statement is never trusted or allowed to fail registration.
  • Passkeys are bound to the Console's own domain (first ALLOWED_ORIGINS entry). No new env vars or server secrets.
  • Security emails say whether 2FA is still on; session recordings never capture network bodies.

Testing

  • Backend: new test_passkeys.py plus updated MFA tests (172 in the 2FA suites); full suite passes apart from the 5 tests that fail on main in the dev container.
  • Frontend: jest 516/516, tsc and lint clean.
  • Browser QA with Chromium virtual authenticators (sign-in, add/remove, lost responses, lockouts), Bitwarden against a local Vaultwarden, and WebKit.

Docs: phasehq/docs#274

Passkeys (WebAuthn, via py_webauthn and @simplewebauthn/browser) work as a
second factor alongside or instead of TOTP, sharing one set of recovery
codes issued with the first factor and deleted with the last. Adding,
removing or changing a factor needs proof of an existing one on top of a
fresh session; sign-in offers every method the account has, and a code
lockout leaves passkeys usable.

The account page gets a review step for new passkeys (provider and name
inferred from the AAGUID), method tabs for proofs, a split TOTP setup
wizard, passkey details, and a redesigned second-factor sign-in screen.
Passkeys are bound to the Console's own domain (first ALLOWED_ORIGINS
entry); no new settings or server secrets.
Comment thread backend/api/views/auth_mfa.py Fixed
@nimish-ks nimish-ks self-assigned this Oct 30, 2026
nimish-ks and others added 2 commits October 30, 2026 20:29
The verify endpoint returned the PasskeyError's own text and code. They
were always fixed strings, but the response now takes both from the
message table so no exception data reaches the client.
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