# Vaultwarden Two-Factor Authentication: Available Methods and YubiKey, Duo, WebAuthn Setup Guide

> Explore Vaultwarden two-factor authentication. Learn about TOTP, Email, Duo, YubiKey, and WebAuthn setup. Configure hardware keys and enterprise security integrations with Vaultwarden.

- Repository: [Daniel García/vaultwarden](https://github.com/dani-garcia/vaultwarden)
- Tags: how-to-guide
- Published: 2026-03-07

---

**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`](https://github.com/dani-garcia/vaultwarden/blob/main/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** | `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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/config.rs) (lines 34-42) using these environment variables:

```yaml
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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/two_factor/yubikey.rs). The registration logic creates a `TwoFactor` entry with type `3`:

```rust
// 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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/config.rs) (lines 46-57):

```yaml
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`](https://github.com/dani-garcia/vaultwarden/blob/main/duo_oidc.rs). More secure and compatible with modern CSP policies.
- **Iframe**: Enabled when `duo_use_iframe = true`. Injects Duo WebSDK with relaxed CSP `frame-src` headers defined in [`src/util.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/two_factor/duo.rs). The handler validates credentials against Duo's API before storage:

```bash
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`](https://github.com/dani-garcia/vaultwarden/blob/main/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:

```rust
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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/two_factor/webauthn.rs) follows three steps:

1. **Challenge Generation**: Server creates a `WebauthnRegisterChallenge` stored as a temporary `TwoFactor` record
2. **Client Attestation**: Browser calls `navigator.credentials.create()` with the challenge
3. **Server Validation**: Server parses the response using `webauthn_rs::prelude::Credential` and stores the JSON blob:

```rust
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:**

```javascript
// 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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/identity.rs) routes 2FA validation based on the user's selected `TwoFactorType`:

```rust
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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/two_factor.rs)
- **YubiKey requires Yubico API credentials**: Set `YUBICO_CLIENT_ID`, `YUBICO_SECRET_KEY`, and `ENABLE_YUBICO` environment 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=true` and specific CSP headers in [`src/util.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/util.rs)
- **WebAuthn requires HTTPS domain**: Must set `DOMAIN` environment variable; automatically migrates legacy U2F registrations on startup via `migrate_u2f_to_webauthn` in [`main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/main.rs)
- **Centralized validation**: All 2FA methods route through [`src/api/identity.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/identity.rs) using the `TwoFactorType` enum dispatcher

## Frequently Asked Questions

### How do I migrate from legacy U2F to WebAuthn in Vaultwarden?

Migration happens automatically. When Vaultwarden starts, [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/util.rs) to allow `frame-src` from Duo's CDN. OIDC is recommended for stronger security and CSP compliance.