Privacy Tools Guide

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.

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.

What is FIDO2?

FIDO2 (Fast Identity Online) is an open standard (developed by the FIDO Alliance) for passwordless authentication. It combines two specs:

  1. WebAuthn - Browser/web API for registering and using authenticators
  2. 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:

  1. 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
  2. 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:

  1. Visit appleid.apple.com
  2. Click “Create Passkey”
  3. Face ID or Touch ID prompt appears
  4. You authenticate with biometric
  5. 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):

Limited support (okay):

No support (problematic):

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:

  1. You click link, see fake bank login page
  2. You enter password, credentials stolen
  3. Attacker logs into real bank with your password

With passkey:

  1. You click link, see fake bank login page
  2. Click “login with passkey”
  3. Browser checks: This domain isn’t the real bank
  4. Passkey refuses to activate (browser sees mismatch)
  5. Login fails, you’re safe

Scenario 2: Data breach

Attacker hacks service.com database, steals password hashes.

With password:

  1. Hacker attempts to crack password hashes (brute force, dictionary attack)
  2. Common passwords crack instantly
  3. Attacker tries cracked password on other services (credit cards, email, bank)
  4. Account takeover

With passkey:

  1. Hacker steals public key hashes (useless without private key)
  2. Hacker can’t authenticate even with the public key
  3. Passkey remains secure on your device
  4. 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:

  1. Verify email address (send verification code)
  2. Grant temporary password (24 hours, single use)
  3. User logs in, creates new passkey
  4. Temporary password revoked

Balance security (not too easy to recover) with usability (not impossible).

When to Use Passkeys vs Passwords

Use passkeys for:

Keep passwords for:

Real-World Examples (2026)

Google Account (Gmail, Drive, etc):

Apple ID:

Microsoft Account (Outlook, OneDrive, etc):

GitHub:

Future of Authentication

By 2027-2028, expect:

Built by theluckystrike — More at zovo.one