Passwords are dying. FIDO2-based passkeys (biometric or hardware key authentication) eliminate the attack vectors that plague password-based systems: phishing, credential reuse, and brute force. But passkeys aren’t a universal solution—they require device setup, complicate account recovery, and have limited platform support. This guide compares passkeys and passwords across security, usability, and implementation complexity. We cover FIDO2 technical standards, WebAuthn browser support, migration strategies, and real-world scenarios where passkeys shine (corporate environments, high-security accounts) and where passwords remain practical (legacy systems, financial recovery). Understanding the tradeoffs helps you choose the right authentication method for your use case.
The Fundamental Difference: Public Key vs Shared Secret
Passwords are shared secrets:
You and the service agree on a string of characters. If anyone else learns that string, they can log in as you.
- Everyone with the password can authenticate
- Password reuse means one breach compromises multiple services
- Phishing works because the password is the complete authentication secret
- Brute force and dictionary attacks are possible
Passkeys use public key cryptography:
A cryptographic key pair is generated on your device. Your device keeps the private key (never shared). The service stores your public key (useless without the private key). To authenticate, you prove you have the private key without ever sending it.
- Only your device can authenticate (private key never leaves it)
- Each service gets a unique key pair (reuse impossible)
- Phishing fails because the service can’t spoof your private key
- Brute force is cryptographically impossible (2^256 keyspace)
What is FIDO2?
FIDO2 (Fast Identity Online) is an open standard (developed by the FIDO Alliance) for passwordless authentication. It combines two specs:
- WebAuthn - Browser/web API for registering and using authenticators
- CTAP (Client to Authenticator Protocol) - Communication protocol between your device and authenticator
FIDO2 flow:
User Registration:
1. User requests passkey registration on service.com
2. Browser asks operating system to create credential
3. OS prompts for biometric (fingerprint, face) or security key
4. Device generates public/private key pair
5. Public key sent to service.com (private key stays on device)
6. Service stores public key for future authentication
User Authentication:
1. User navigates to service.com login
2. Service asks for passkey authentication
3. Browser triggers OS biometric/key prompt
4. User provides fingerprint/face/key interaction
5. Device uses private key to sign a challenge
6. Signed challenge sent to service.com
7. Service verifies signature matches stored public key
8. User logged in
Passkeys: Practical Security
A passkey is a FIDO2 credential stored on your device (phone, laptop, hardware key). Each passkey is unique to the service—you can’t accidentally reuse the same passkey for multiple services (unlike password reuse).
Passkey types:
- Platform passkeys (built-in):
- Uses device biometrics (Touch ID, Face ID, Windows Hello)
- Stored securely on device hardware (Secure Enclave, TPM)
- No additional hardware needed
- Examples: Apple Keychain, Windows Credential Manager, Android Passkeys
- Roaming authenticators (external hardware):
- Physical security keys (Yubikey, Google Titan, Varonis)
- Bluetooth, NFC, or USB connection
- Work across devices (any phone, laptop, tablet)
- More secure (compromised device doesn’t compromise key)
- Cost: $20-70 per key
Real example: Apple passkey registration
You create an Apple ID with passkey:
- Visit appleid.apple.com
- Click “Create Passkey”
- Face ID or Touch ID prompt appears
- You authenticate with biometric
- Passkey created—no password needed
Next time you log in, just Face ID/Touch ID. No password required.
Platform Support for Passkeys (2026)
Full passkey support (good):
- macOS 13.3+: Keychain passkeys, iCloud syncing
- iOS 16+: Passkeys synced across Apple devices via iCloud
- Android 9+: Passkeys stored in Google Play Services
- Windows 11 22H2+: Windows Hello passkeys, PIN fallback
- Chrome 90+: Full WebAuthn support
- Safari 15.1+: Full WebAuthn support
- Firefox 90+: Full WebAuthn support
Limited support (okay):
- iPhone 12-15 with older iOS: Can use security key, not biometric passkey
- Android 8: Can use external security key
- Edge 90+: Full support, same as Chrome
No support (problematic):
- Internet Explorer: No WebAuthn
- Safari on iOS 15.0: Partial support, biometric limited
- Legacy browsers: Firefox <90, Chrome <90
Real-world support check (for developers):
Test browser WebAuthn support:
// Check if browser supports WebAuthn
if (window.PublicKeyCredential) {
console.log("✓ WebAuthn supported");
// Check platform authenticator availability (biometric)
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(available => {
if (available) {
console.log("✓ Biometric passkey supported");
} else {
console.log("⚠ Security key only (no biometric)");
}
});
} else {
console.log("✗ WebAuthn not supported - password required");
}
Security Comparison: Passkeys vs Passwords
| Attack Vector | Passwords | Passkeys |
|---|---|---|
| Phishing | Vulnerable (user types password to fake site) | Immune (service verified by browser, passkey only works for legitimate domain) |
| Password reuse | Vulnerable (same password on multiple sites) | Immune (unique key per service) |
| Data breach | Compromised (hashed passwords cracked with dict attack) | Safe (service only has public key, useless without private key) |
| Brute force | Vulnerable (weak passwords can be guessed) | Immune (2^256 keyspace) |
| Credential theft malware | Vulnerable (keyloggers capture password) | Somewhat resistant (passkey requires device interaction, malware can’t silently steal) |
| Man-in-the-middle | Vulnerable (password intercepted in transit) | Protected (passkey is signed response, not the secret itself) |
| Account recovery | Easy (reset password via email) | Hard (requires backup passkeys or recovery codes) |
| Device theft | Passwords still work elsewhere | Passkeys on device are locked (biometric required) |
Real-World Attack Prevention: Examples
Scenario 1: Phishing attack
Attacker sends email pretending to be your bank: “Click here to verify your account.”
With password:
- You click link, see fake bank login page
- You enter password, credentials stolen
- Attacker logs into real bank with your password
With passkey:
- You click link, see fake bank login page
- Click “login with passkey”
- Browser checks: This domain isn’t the real bank
- Passkey refuses to activate (browser sees mismatch)
- Login fails, you’re safe
Scenario 2: Data breach
Attacker hacks service.com database, steals password hashes.
With password:
- Hacker attempts to crack password hashes (brute force, dictionary attack)
- Common passwords crack instantly
- Attacker tries cracked password on other services (credit cards, email, bank)
- Account takeover
With passkey:
- Hacker steals public key hashes (useless without private key)
- Hacker can’t authenticate even with the public key
- Passkey remains secure on your device
- Account remains locked down
WebAuthn Implementation Details
For developers, here’s how passkey registration and authentication work:
Registration (creating passkey):
// Initiate passkey registration
async function registerPasskey() {
const response = await fetch('/api/register/start', { method: 'POST' });
const options = await response.json();
// Browser prompts for biometric/security key
const credential = await navigator.credentials.create({
publicKey: {
challenge: new Uint8Array(options.challenge),
rp: { name: "MyService", id: "myservice.com" },
user: {
id: new Uint8Array([1, 2, 3, ...]),
name: "user@example.com",
displayName: "User Name"
},
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
authenticatorSelection: {
authenticatorAttachment: "platform" // Use device biometric
},
attestation: "direct"
}
});
// Send credential to server
await fetch('/api/register/complete', {
method: 'POST',
body: JSON.stringify({
id: credential.id,
rawId: credential.rawId,
response: credential.response
})
});
}
Authentication (logging in with passkey):
// Initiate passkey authentication
async function authenticateWithPasskey() {
const response = await fetch('/api/auth/start', { method: 'POST' });
const options = await response.json();
// Browser prompts for biometric
const assertion = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array(options.challenge),
rpId: "myservice.com",
timeout: 60000,
userVerification: "preferred"
}
});
// Send signed response to server
const authResponse = await fetch('/api/auth/complete', {
method: 'POST',
body: JSON.stringify({
id: assertion.id,
response: assertion.response
})
});
if (authResponse.ok) {
// User authenticated!
}
}
Server-side verification:
# Python example (using webauthn library)
from webauthn import (
verify_registration_response,
verify_authentication_response,
options_reg,
options_auth
)
# Registration verification
verified_reg = verify_registration_response(
credential=credential_data,
expected_challenge=session_challenge,
expected_origin="https://myservice.com",
expected_rp_id="myservice.com"
)
# Store public key
user.credential_public_key = verified_reg.credential_public_key
# Authentication verification
verified_auth = verify_authentication_response(
credential=credential_data,
expected_challenge=session_challenge,
expected_origin="https://myservice.com",
expected_rp_id="myservice.com",
credential_public_key=user.credential_public_key
)
# User authenticated!
Migration Strategy: Passwords to Passkeys
You don’t need to force passkeys immediately. A practical migration:
Phase 1 (Now): Offer passkeys as optional
Users keep passwords. Passkey option available for security-conscious users.
Phase 2 (6 months): Passkey default
New users registered with passkey by default. Existing users offered passkey upgrade.
Phase 3 (12 months): Passwords deprecated
Passwords still work but deprecated. Push existing password users to migrate.
Implementation example:
// Login page shows both options
<button onclick="loginWithPassword()">Login with password</button>
<button onclick="loginWithPasskey()">Login with passkey</button>
// New user registration defaults to passkey
async function registerNewUser() {
// Check if passkeys available
if (await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()) {
// Offer passkey registration first
await registerPasskey();
} else {
// Fallback to password
await registerPassword();
}
}
// Existing users get upgrade prompt
function promptPasskeyUpgrade() {
if (await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()) {
showModal("Secure your account with passkey", "Upgrade now");
}
}
Account Recovery Without Passwords
Passkeys complicate account recovery (can’t reset password). Solutions:
Option 1: Backup passkeys
Users create 2-3 backup passkeys (stored in different locations):
Primary passkey: iPhone Face ID
Backup passkey 1: iPad Touch ID
Backup passkey 2: Security key (in safe)
If primary passkey unavailable, use backup.
Option 2: Recovery codes
Generate unique, one-time recovery codes when passkey is created:
Recovery Codes:
ABCD-EFGH-IJKL-MNOP
QRST-UVWX-YZAB-CDEF
GHIJ-KLMN-OPQR-STUV
User stores these safely. If all passkeys lost, recovery code regains access (one-time use).
Option 3: Email recovery + temporary password
User initiates account recovery:
- Verify email address (send verification code)
- Grant temporary password (24 hours, single use)
- User logs in, creates new passkey
- Temporary password revoked
Balance security (not too easy to recover) with usability (not impossible).
When to Use Passkeys vs Passwords
Use passkeys for:
- High-security accounts (corporate, banking, email)
- Users with compatible devices (iPhones, latest Android)
- Web apps where you control registration (can mandate passkeys)
- Users who value security over convenience
Keep passwords for:
- Legacy systems (old software, browsers)
- Financial recovery (need email-based reset)
- Users without compatible devices
- Services requiring WCAG accessibility compliance
- Hybrid approaches (passkeys optional, passwords fallback)
Real-World Examples (2026)
Google Account (Gmail, Drive, etc):
- Passkey support: ✓ Full
- Password: Still allowed (for legacy)
- Google recommends passkey for new users
- Can add passkey to existing account
Apple ID:
- Passkey support: ✓ Full
- Password: Deprecated (removed from new accounts)
- Existing accounts: Passkey strongly recommended
- Recovery: Backup passkeys or recovery codes
Microsoft Account (Outlook, OneDrive, etc):
- Passkey support: ✓ Full (Windows Hello, security keys)
- Password: Still allowed
- Mixed support (legacy Microsoft accounts still password-only)
GitHub:
- Passkey support: ✓ Full (WebAuthn support)
- Password: Still primary for developer accounts
- Passkey available for high-security access
Future of Authentication
By 2027-2028, expect:
- Passkeys becoming standard on all major services
- Password-only accounts considered legacy
- Regulatory requirements for high-security services to support passkeys
- Hardware security keys becoming more affordable and common
- Cross-service credential syncing improving
Related Articles
- WebAuthn vs FIDO2 vs Passkeys: Key Differences Explained
- Proton Pass Passkeys Support Review 2026
- TOTP vs FIDO2 Authentication Explained: A Developer’s Guide
- WebAuthn Implementation Guide for Developers
- How to Export Passwords from Any Manager
Built by theluckystrike — More at zovo.one