mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-07-16 17:02:14 -05:00
[PR #7297] [CLOSED] Add webauthn login (consolidated) #31581
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/dani-garcia/vaultwarden/pull/7297
Author: @zaid-marji
Created: 6/3/2026
Status: ❌ Closed
Base:
main← Head:webauthn-login📝 Commits (10+)
c0589bbplaywright: fix stale Master password selectors against bundled web vaultc02ced4playwright: fix TOTP setup flow against bundled web vaultf30847dplaywright: serve the test Vaultwarden over HTTPSa3fb027gitignore: catch stray playwright artifacts at the repo root9186ec2playwright: fix stale org-nav selectors against bundled web vault0c009d6playwright: fix invitation-accepted toast text + SSO existing-account redirectad582b4playwright: centralize web-vault selectors into shared setup helpers88ab514playwright: centralize 2FA challenge handling1cdf0b7Feat add webauthn login5760adeFix and harden the passkey login implementation📊 Changes
57 files changed (+6365 additions, -202 deletions)
View changed files
📝
.env.template(+4 -0)📝
.gitignore(+6 -0)📝
Cargo.toml(+2 -1)📝
README.md(+4 -0)➕
migrations/mysql/2026-02-12-000000_add_web_authn_credentials/down.sql(+1 -0)➕
migrations/mysql/2026-02-12-000000_add_web_authn_credentials/up.sql(+15 -0)➕
migrations/mysql/2026-05-21-000000_add_web_authn_login_challenges/down.sql(+1 -0)➕
migrations/mysql/2026-05-21-000000_add_web_authn_login_challenges/up.sql(+7 -0)➕
migrations/postgresql/2026-02-12-000000_add_web_authn_credentials/down.sql(+1 -0)➕
migrations/postgresql/2026-02-12-000000_add_web_authn_credentials/up.sql(+14 -0)➕
migrations/postgresql/2026-05-21-000000_add_web_authn_login_challenges/down.sql(+1 -0)➕
migrations/postgresql/2026-05-21-000000_add_web_authn_login_challenges/up.sql(+7 -0)➕
migrations/sqlite/2026-02-12-000000_add_web_authn_credentials/down.sql(+1 -0)➕
migrations/sqlite/2026-02-12-000000_add_web_authn_credentials/up.sql(+14 -0)➕
migrations/sqlite/2026-05-21-000000_add_web_authn_login_challenges/down.sql(+1 -0)➕
migrations/sqlite/2026-05-21-000000_add_web_authn_login_challenges/up.sql(+7 -0)📝
playwright/.env.template(+2 -1)📝
playwright/README.md(+7 -0)📝
playwright/compose/playwright/Dockerfile(+1 -1)📝
playwright/compose/warden/Dockerfile(+12 -0)...and 37 more files
📄 Description
Summary
Adds the long-requested "Log in with passkey" feature: passwordless
account login via a discoverable WebAuthn credential, with optional
PRF-based vault unlock. This consolidates the prior efforts in #6820
(@samos667) and #5929 (@zUnixorn) into a single review-ready PR, as the
maintainers suggested on those threads. The foundational commit retains
@samos667 as author and credits @zUnixorn via
Co-authored-by:; theremaining commits are my own.
What it does
grant_type=webauthn): a discoverable, user-verifiedWebAuthn assertion authenticates the user. Per Bitwarden's model the
authenticator's user-verification is the second factor, so TOTP is not
re-prompted.
be unlocked with the passkey. Exposed via
webAuthnPrfOptionson/syncand
UserDecryptionOptionsat login, matching the Bitwarden web client.(
/api/webauthn*), each gated by password/OTP.post_rotatekeyre-wraps the account-key-wrapped PRFmaterial so rotating the account key no longer silently breaks passkey unlock.
prfStatusimplemented (Enabled / Supported / Unsupported), replacingthe previous placeholder.
pm-2035-passkey-unlockconfig flag andsuppressed under
SSO_ONLYor a WebAuthn-incompatibleDOMAIN.Threat model
The login and key-handling paths defend against:
challenges; signature-counter advance with CAS rollback protection).
(uniform
Passkey authentication failedresponse across every failurebranch — unknown user, missing credential, verification failure, account
disabled or unverified — gated by an IP-shared login rate limit).
per-user-scoped at the DB layer; credential ID hashes are unique per user
rather than global).
post_rotatekeyre-wraps eachPRF credential's account-key-wrapped material under the new account key as
part of the rotation; a concurrent passkey delete during rotation is
tolerated as a best-effort degraded no-op).
deliberately omit the
UserFailedLogInevent so an attacker submittingforged user-handles cannot create audit entries on the victim's account).
Hardening
A few changes also tighten shared paths beyond passkey login — flagged here
since they affect more than the new feature:
enforce_2fa_policy): atransient DB error while loading a user's Require-2FA org memberships
previously collapsed to an empty set and silently skipped enforcement; it now
propagates. This closes the same gap for password login and recovery-code use.
grant types, replacing the previous password-only per-grant check.
is_webauthn_2fa_supportedare hardenedagainst a malformed
DOMAINthat could previously panic a worker thread(
.expect()→ graceful refusal); every WebAuthn entry point is now gated.Resultinstead of swallowingerrors into
None, so a transient failure surfaces rather than being misreadas "not found".
Database
Two new additive tables (
web_authn_credentials,web_authn_login_challenges) with migrations for all three backends. Noexisting data is migrated —
webauthn-rs0.5 is already inmain, so the2FA-object migration discussed on #5929 does not apply here.
Testing
current bundled web-vault; the new E2E specs run on that harness, so whoever
reviews/merges this may want to take #7248 alongside it. The first 8 commits
of this PR's diff are #7248's; the webauthn-specific work starts at
1cdf0b7b.cargo fmt/clippy/testand the Playwright E2E suite pass. New Rustunit tests cover the PRF/UserDecryption response shapes, challenge
freshness + single-use, and passkey-rotation validation.
enrol → login → lock → unlock → rotate → remove lifecycle, the rejection
paths (disabled account, deleted credential, runtime-unusable 2FA), SSO_ONLY
denial, and malformed-input rejection.
Compatibility
Wire shapes (
UserDecryptionOptions,webAuthnPrfOptions,GetPrfStatus)follow the upstream Bitwarden server so the official web client works
unchanged.
I'm glad to answer questions or make any changes needed to help move this
along — just let me know.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.