Vaultwarden Two-Factor Authentication: Available Methods and YubiKey, Duo, WebAuthn Setup Guide
Vaultwarden supports eight two-factor authentication methods—including TOTP, Email, Duo, YubiKey, and WebAuthn—and requires specific environment variables or API configurations to enable hardware key and enterprise security integrations.
Vaultwarden, the open-source Bitwarden-compatible password manager written in Rust, provides a comprehensive 2FA ecosystem defined in the TwoFactorType enum. This guide examines the available authentication methods and provides exact configuration steps for YubiKey OTP, Duo Security, and WebAuthn based on the dani-garcia/vaultwarden source code.
Available Two-Factor Authentication Methods in Vaultwarden
The complete list of supported providers is defined in src/db/models/two_factor.rs (lines 25-35), where the TwoFactorType enum assigns integer values to each method:
| Method | Enum Value | Description |
|---|---|---|
| Authenticator | Authenticator = 0 |
Standard TOTP codes (Google Authenticator, Authy) |
Email = 1 |
One-time codes sent via SMTP | |
| Duo | Duo = 2 |
Duo Security OIDC or iframe flow |
| YubiKey | YubiKey = 3 |
YubiKey OTP validation via Yubico API |
| U2F (legacy) | U2f = 4 |
Deprecated; auto-migrated to WebAuthn on startup |
| Remember | Remember = 5 |
"Remember this device" cookie |
| Organization Duo | OrganizationDuo = 6 |
Duo scoped to specific organizations |
| WebAuthn | Webauthn = 7 |
Modern FIDO2/WebAuthn hardware keys and platform authenticators |
| Recovery Code | RecoveryCode = 8 |
Single-use backup codes |
All 2FA data persists in the twofactor table via the TwoFactor model (src/db/models/two_factor.rs, lines 14-22), where the atype column stores the integer value corresponding to the enum.
Configuring YubiKey OTP Authentication
YubiKey support requires Yubico API credentials and server-side validation against Yubico's verification service.
Server Configuration
Enable YubiKey support in src/config.rs (lines 34-42) using these environment variables:
environment:
ENABLE_YUBICO: "true"
YUBICO_CLIENT_ID: "12345"
YUBICO_SECRET_KEY: "your-secret-key"
YUBICO_SERVER: "https://api.yubico.com/wsapi/2.0/verify"
The _enable_yubico flag must be true for the API endpoints to accept YubiKey registrations.
User Registration Process
When an administrator enables YubiKey for a user, the UI sends a POST to /admin/users/{id}/two-factor/yubikey, handled by src/api/core/two_factor/yubikey.rs. The registration logic creates a TwoFactor entry with type 3:
// src/api/core/two_factor/yubikey.rs - registration endpoint
let yubikey_type = TwoFactorType::YubiKey as i32;
let tf = match TwoFactor::find_by_user_and_type(&user.uuid, yubikey_type, &conn).await {
None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()),
Some(t) => t,
};
tf.save(&conn).await?;
Validation Flow
During login, src/api/identity.rs (lines 746-754) dispatches to yubikey::validate_yubikey_login, which verifies the OTP against the configured Yubico verification server using the client ID and secret key.
Configuring Duo Security
Vaultwarden supports Duo via both modern OIDC redirect flow and legacy iframe embedding.
Server Configuration
Duo settings are defined in src/config.rs (lines 46-57):
environment:
DUO_IKEY: "DIXXXXXXXXXXXXXXXXXX"
DUO_SKEY: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
DUO_HOST: "api-XXXX.duosecurity.com"
DUO_USE_IFRAME: "false" # Set true for legacy iframe mode
OIDC vs Iframe Modes
- OIDC (default): Uses OAuth2 redirect flow with
duo_oidc.rs. More secure and compatible with modern CSP policies. - Iframe: Enabled when
duo_use_iframe = true. Injects Duo WebSDK with relaxed CSPframe-srcheaders defined insrc/util.rs(lines 102-108).
Activation Process
Administrators activate Duo via the /admin/duo/activate endpoint implemented in src/api/core/two_factor/duo.rs. The handler validates credentials against Duo's API before storage:
curl -X POST "https://vaultwarden.example.com/api/admin/duo/activate" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"duo_ikey": "DIXXXXXXXXXXXXXXXXXX",
"duo_skey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"duo_host": "api-12345678.duosecurity.com",
"duo_use_iframe": false
}'
The OIDC flow (src/api/core/two_factor/duo_oidc.rs) stores temporary context via TwoFactorDuoContext::save(state, email, nonce, CTX_VALIDITY_SECS, conn).await, then exchanges the authorization code:
let token = client.exchange_code(&code).await?;
Configuring WebAuthn (FIDO2)
WebAuthn provides modern FIDO2 authentication for hardware keys, Touch ID, and Windows Hello.
Prerequisites
WebAuthn requires a valid HTTPS domain configured via the DOMAIN environment variable. The helper is_webauthn_2fa_supported in src/config.rs (lines 1647-1650) validates this condition before exposing registration endpoints.
Registration Flow
The registration process in src/api/core/two_factor/webauthn.rs follows three steps:
- Challenge Generation: Server creates a
WebauthnRegisterChallengestored as a temporaryTwoFactorrecord - Client Attestation: Browser calls
navigator.credentials.create()with the challenge - Server Validation: Server parses the response using
webauthn_rs::prelude::Credentialand stores the JSON blob:
let regs = serde_json::from_str(&tf.data)?;
let mut new_regs = regs.clone();
new_regs.push(WebauthnRegistration { /* fields */ });
TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&new_regs)?)
.save(&conn).await?;
Client-side JavaScript implementation:
// 1. Obtain challenge from server
const challenge = await fetch('/api/two-factor/webauthn/register-challenge')
.then(r => r.json());
// 2. Create credential via WebAuthn API
const cred = await navigator.credentials.create({
publicKey: {
challenge: Uint8Array.from(atob(challenge), c => c.charCodeAt(0)),
rp: { name: "Vaultwarden" },
user: {
id: Uint8Array.from(atob(userId), c => c.charCodeAt(0)),
name: email,
displayName: email
},
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
timeout: 60000,
attestation: "direct"
}
});
// 3. Send attestation to server
await fetch('/api/two-factor/webauthn/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(cred)
});
Migration from Legacy U2F
On startup, src/main.rs (lines 89-93) executes TwoFactor::migrate_u2f_to_webauthn, which automatically converts legacy U2F registrations (type 4) to WebAuthn format (type 7). This migration copies credentials from U2f rows into the WebAuthn table and disables the old entries, ensuring seamless upgrades without user interaction.
Implementation Architecture
The central login dispatcher in src/api/identity.rs routes 2FA validation based on the user's selected TwoFactorType:
match TwoFactorType::from_i32(selected_id) {
Some(TwoFactorType::Authenticator) => { /* TOTP validation */ }
Some(TwoFactorType::Webauthn) => webauthn::validate_webauthn_login(...),
Some(TwoFactorType::YubiKey) => yubikey::validate_yubikey_login(...),
Some(TwoFactorType::Duo) => duo::validate_duo_login(...),
// ...
}
Security headers for Duo iframe and WebAuthn connector pages are constructed in src/util.rs, ensuring proper CSP directives for external scripts and frames.
Summary
- Eight methods available: Vaultwarden supports TOTP, Email, Duo, YubiKey, WebAuthn, Recovery Codes, and legacy U2F (auto-migrated) as defined in
src/db/models/two_factor.rs - YubiKey requires Yubico API credentials: Set
YUBICO_CLIENT_ID,YUBICO_SECRET_KEY, andENABLE_YUBICOenvironment variables; validation occurs against Yubico's verification service - Duo supports OIDC and iframe modes: Modern OIDC flow is default; legacy iframe mode requires
DUO_USE_IFRAME=trueand specific CSP headers insrc/util.rs - WebAuthn requires HTTPS domain: Must set
DOMAINenvironment variable; automatically migrates legacy U2F registrations on startup viamigrate_u2f_to_webauthninmain.rs - Centralized validation: All 2FA methods route through
src/api/identity.rsusing theTwoFactorTypeenum dispatcher
Frequently Asked Questions
How do I migrate from legacy U2F to WebAuthn in Vaultwarden?
Migration happens automatically. When Vaultwarden starts, src/main.rs executes TwoFactor::migrate_u2f_to_webauthn, which reads existing U2F registrations, converts them to WebAuthn format, and stores them as type 7 in the twofactor table. The original U2F entries are then disabled. Users do not need to re-register their hardware keys.
Can I use both YubiKey OTP and WebAuthn simultaneously?
Yes. Vaultwarden stores each 2FA method as a separate row in the twofactor table identified by the atype column (3 for YubiKey, 7 for WebAuthn). A user can register multiple WebAuthn devices and a YubiKey OTP, then select their preferred method during login. The system validates against the specific type chosen in the authentication request.
Why does WebAuthn registration fail with a domain error?
WebAuthn requires a valid HTTPS domain configured via the DOMAIN environment variable. The configuration checker is_webauthn_2fa_supported in src/config.rs validates this; if unset or using HTTP only, the WebAuthn endpoints return errors. Ensure your Vaultwarden instance uses HTTPS with a proper domain name, not an IP address or localhost, for FIDO2 compatibility.
What is the difference between Duo OIDC and iframe modes?
OIDC mode (duo_use_iframe = false) redirects the user to Duo's hosted authentication page using OAuth2 flow, then returns to Vaultwarden with an authorization code exchanged via src/api/core/two_factor/duo_oidc.rs. Iframe mode embeds Duo's WebSDK directly in the Vaultwarden UI, requiring relaxed Content Security Policy headers in src/util.rs to allow frame-src from Duo's CDN. OIDC is recommended for stronger security and CSP compliance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →