Passkeys
Vault uses passkeys (WebAuthn) for authentication, providing phishing-resistant, passwordless login.
What Are Passkeys?
Passkeys are cryptographic credentials that replace passwords:
- Phishing-resistant: Bound to specific domains
- No shared secrets: Public key cryptography
- Biometric: Unlock with fingerprint or face
- Cross-device: Sync across your devices
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your Device │ │ Authenticator │ │ Vault Server │
│ │ │ (TPM/Secure │ │ │
│ Browser/App │◀───▶│ Enclave) │◀───▶│ Stores Public │
│ │ │ │ │ Key Only │
│ Triggers Auth │ │ Signs Challenge│ │ Verifies Sig │
└─────────────────┘ └─────────────────┘ └─────────────────┘Why Passkeys?
vs. Passwords
| Feature | Passwords | Passkeys |
|---|---|---|
| Phishing | Vulnerable | Resistant |
| Reuse attacks | Vulnerable | Immune |
| Brute force | Vulnerable | Immune |
| Data breaches | Exposed | Only public key |
| User friction | High | Low |
vs. 2FA
| Feature | TOTP/SMS | Passkeys |
|---|---|---|
| Phishing | Vulnerable | Resistant |
| SIM swap | Vulnerable (SMS) | Immune |
| Code entry | Required | Not needed |
| Single step | No | Yes |
How It Works
Registration
- Server generates random challenge
- Authenticator creates key pair
- Private key stored in secure hardware
- Public key sent to server
// Server generates options
const options = await generateRegistrationOptions({
rpName: "Vault",
rpID: "vault.oxc.sh",
userID: userId,
userName: email,
authenticatorSelection: {
residentKey: "required",
userVerification: "required"
}
});
// Client creates credential
const credential = await navigator.credentials.create({
publicKey: options
});
// Server stores public key
await storeCredential(userId, credential);Authentication
- Server generates random challenge
- Authenticator signs challenge with private key
- User verifies with biometric
- Server verifies signature with public key
// Server generates options
const options = await generateAuthenticationOptions({
rpID: "vault.oxc.sh",
allowCredentials: userCredentials,
userVerification: "required"
});
// Client signs challenge
const assertion = await navigator.credentials.get({
publicKey: options
});
// Server verifies signature
const verified = await verifyAuthenticationResponse({
response: assertion,
expectedChallenge: challenge,
expectedOrigin: "https://vault.oxc.sh",
expectedRPID: "vault.oxc.sh",
credential: storedCredential
});Phishing Resistance
Passkeys are bound to the relying party (RP) origin:
✅ https://vault.oxc.sh → Passkey works
❌ https://vault.oxc.sh.fake → Passkey refuses to sign
❌ https://voult.oxc.sh → Passkey refuses to signThe authenticator checks:
- Origin: Must match registered domain
- RP ID: Must match registered RP ID
- TLS: Must be HTTPS
Even if you're tricked, your passkey won't authenticate to a fake site.
User Verification
Vault requires user verification for every authentication:
| Method | Platform |
|---|---|
| Touch ID | macOS, iOS |
| Face ID | iOS |
| Windows Hello | Windows |
| Fingerprint | Android |
| PIN | All platforms |
This ensures someone with physical access to your device still needs biometric or PIN.
Supported Authenticators
Platform Authenticators (Built-in)
| Platform | Authenticator | Sync |
|---|---|---|
| Apple | iCloud Keychain | iCloud |
| Google Password Manager | Google Account | |
| Windows | Windows Hello | Microsoft Account |
Roaming Authenticators (External)
| Device | Type |
|---|---|
| YubiKey | USB/NFC |
| Titan Key | USB/NFC |
| Feitian | USB/NFC |
PRF Extension
Vault uses the WebAuthn PRF extension to derive vault encryption keys:
const credential = await navigator.credentials.get({
publicKey: {
...options,
extensions: {
prf: {
eval: {
first: saltBytes
}
}
}
}
});
// PRF output used to derive vault key
const prfOutput = credential.getClientExtensionResults().prf?.results?.first;- Hardware-bound encryption key
- No master password needed (if PRF supported)
- Key never leaves secure hardware
- Not all authenticators support PRF
- Falls back to master password if unavailable
CLI Authentication
The CLI can't perform WebAuthn directly (no browser context), so it uses browser delegation:
- CLI creates auth session on server
- CLI opens browser with session ID
- User completes passkey auth in browser
- Browser completes session
- CLI polls and receives token
See CLI Authentication for details.
Security Considerations
Lost Authenticator
If you lose your passkey authenticator:
- Platform passkeys sync automatically
- Register backup authenticator recommended
- Account recovery requires identity verification
Compromised Authenticator
If your authenticator is compromised:
- Revoke credential in Vault settings
- Re-register with new authenticator
- No password to change
Platform Security
Passkey security depends on:
- Device lock (PIN/biometric)
- Platform integrity
- Secure enclave implementation
Browser Support
| Browser | Platform Passkeys | Roaming (USB) |
|---|---|---|
| Chrome 108+ | ✅ | ✅ |
| Safari 16+ | ✅ | ✅ |
| Firefox 122+ | ✅ | ✅ |
| Edge 108+ | ✅ | ✅ |
Related
- Zero-Knowledge Security - Overall security model
- CLI Authentication - CLI passkey flow
- Authentication API - WebAuthn endpoints