WebAuthn and FIDO2

Passwords are the weakest link in most authentication systems. They get phished, reused across services, and leaked in database breaches. FIDO2 and WebAuthn replace passwords with public-key cryptography — the same primitives covered earlier in this track, applied to the specific problem of proving identity.

The Password Problem

Passwords fail in three specific ways that no amount of password policy can fix:

Phishing: a user enters their password into a convincing fake login page. The password is now in the attacker’s hands. Two-factor codes sent via SMS or TOTP are phishable too — the attacker relays them to the real site in real time.

Credential reuse: users reuse passwords across services. A breach at one site gives attackers credentials that work at dozens of others. Credential stuffing attacks automate this at scale — botnets try leaked username/password pairs against banking, email, and corporate login pages.

Database breaches: even salted and hashed passwords can be cracked if the hashing is weak (MD5, SHA-1, unsalted) or the passwords are common. The RockYou breach exposed 32 million plaintext passwords. The LinkedIn breach exposed 117 million SHA-1 hashes, most of which were cracked within days.

Why MFA Alone Is Not Enough

TOTP (Time-based One-Time Passwords) like those generated by Google Authenticator add a second factor, but they are still phishable. An attacker who controls a fake login page can prompt for the TOTP code and relay it to the real site before it expires. Real-time phishing proxies like Evilginx2 automate this attack. SMS-based codes are worse — they are vulnerable to SIM swapping, SS7 attacks, and social engineering of carrier support staff. The fundamental problem: the user is sending a shared secret to the server, and any intermediary can capture and replay it.

FIDO2 Architecture

FIDO2 is a set of specifications that enable passwordless, phishing-resistant authentication using public-key cryptography. It has two components:

WebAuthn (Web Authentication API): a W3C standard that defines a JavaScript API for creating and using public-key credentials in web browsers. The browser mediates between the website (relying party) and the authenticator.

CTAP2 (Client to Authenticator Protocol): defines how the browser communicates with external authenticators like USB security keys (YubiKeys), Bluetooth devices, or platform authenticators (Touch ID, Windows Hello, Android biometrics).

The three actors in every FIDO2 ceremony: the relying party (the website), the client (the browser), and the authenticator (hardware or platform module that holds the private key). The private key never leaves the authenticator. The relying party only ever sees the public key.

Origin Binding: Why FIDO2 Stops Phishing

The browser includes the origin (e.g., https://bank.example.com) in every WebAuthn request. The authenticator signs over this origin as part of the response. If an attacker sets up a phishing site at https://bank-example.com, the origin in the signature will not match what the real relying party expects. The credential is cryptographically bound to the legitimate origin. Unlike passwords and TOTP codes, a WebAuthn assertion is useless on any domain other than the one it was created for. This is why FIDO2 is described as phishing-resistant by design, not by policy.

Registration Flow

Registration (also called the “create” ceremony) establishes a new credential for a user:

  1. The relying party generates a random challenge and sends it to the browser along with relying party information and user details.
  2. The browser calls navigator.credentials.create() with these parameters, which triggers the authenticator.
  3. The authenticator prompts the user for verification — a fingerprint scan, PIN entry, or physical touch of the security key.
  4. The authenticator generates a new key pair (public and private). The private key is stored on the authenticator. The authenticator creates an attestation object containing the public key, a credential ID, and a signature over the challenge and origin.
  5. The browser returns the attestation object to the relying party.
  6. The relying party verifies the attestation signature, stores the public key and credential ID associated with the user, and discards the challenge.

From this point, the relying party knows the user’s public key but has never seen the private key.

Attestation: Proving Authenticator Legitimacy

Attestation lets the relying party verify that the credential was created by a genuine authenticator, not by software impersonating one. The attestation object includes a certificate chain rooted in the authenticator manufacturer’s CA. A relying party can check that a credential came from, say, a FIPS-certified YubiKey 5 rather than a software emulator. Most consumer deployments use “none” attestation (self-attestation) for privacy reasons — they trust any authenticator. Enterprise deployments may require specific attestation types to enforce hardware security key policies.

Authentication Flow

Authentication (the “get” ceremony) proves the user controls the registered credential:

  1. The relying party generates a random challenge and sends it to the browser along with the credential IDs it has on file for the user.
  2. The browser calls navigator.credentials.get(), which triggers the authenticator.
  3. The authenticator finds the matching credential, prompts the user for verification (biometric, PIN, or touch), and signs the challenge and additional client data (including the origin) with the stored private key.
  4. The browser returns the signed assertion to the relying party.
  5. The relying party verifies the signature against the stored public key. If the signature is valid, the user is authenticated.

No password was transmitted. No shared secret exists between the user and the server. An attacker who compromises the server’s database gets public keys — which are, by definition, public. There is nothing to crack, stuff, or replay.

Replay Protection

The random challenge ensures that each authentication is unique. An attacker who records a valid assertion cannot replay it because the challenge will be different next time. The relying party generates a fresh challenge for each authentication attempt, verifies that the returned challenge matches, and discards it after use. Combined with origin binding, this makes both phishing and replay attacks cryptographically infeasible.

Passkeys: Resident Credentials

Traditional FIDO2 credentials are non-resident (also called server-side discoverable) — the relying party must supply the credential ID so the authenticator knows which key to use. This requires the user to first identify themselves (e.g., by entering a username) before the authenticator is invoked.

Passkeys (resident credentials or client-side discoverable credentials) change this model. The credential is stored on the authenticator in a way that allows the authenticator to enumerate all credentials for a given relying party. The user visits the login page, the browser asks the authenticator for available credentials for that origin, and the authenticator presents a list. The user selects one, verifies with a biometric, and is authenticated — no username, no password.

Platform authenticators (Touch ID, Face ID, Windows Hello, Android) sync passkeys across devices through cloud keychains — iCloud Keychain for Apple, Google Password Manager for Android, Windows Hello for Microsoft. This solves the FIDO2 adoption problem: users no longer need a separate hardware security key for every account.

Security Trade-offs of Synced Passkeys

Syncing passkeys through cloud keychains introduces a trust boundary that hardware-bound credentials avoid. If an attacker compromises a user’s iCloud account, they gain access to synced passkeys. The threat model shifts from physical authenticator security to cloud account security. For high-security environments — banking, government, critical infrastructure — hardware-bound credentials (YubiKeys with no passkey sync) remain the stronger option. For consumer accounts replacing passwords, synced passkeys are a massive security improvement: they eliminate phishing, credential reuse, and database breach risks while being usable enough for mainstream adoption.

The Software Perspective

Implementing WebAuthn as a relying party requires the @simplewebauthn/server (Node.js), py_webauthn (Python), or webauthn-rs (Rust) libraries. The critical steps: generate cryptographically random challenges, verify attestation/assertion signatures, store credential public keys and credential IDs, and enforce origin checks. Do not implement the signature verification yourself — use a vetted library.

Platform authenticators: macOS and iOS expose Touch ID / Face ID through the platform authenticator API. Android uses the device’s biometric system. Windows uses Windows Hello. All major browsers (Chrome, Firefox, Safari, Edge) support the WebAuthn API. A site can offer passkey login today with broad compatibility.

YubiKey and roaming authenticators: YubiKey 5 series devices support FIDO2/WebAuthn, can store up to 25 resident credentials, and communicate via USB, NFC, or Lightning. Roaming authenticators work across devices — the same YubiKey can be plugged into a laptop or tapped against a phone.

Migration strategy: deploy WebAuthn alongside existing password authentication first. Let users register passkeys as a second factor, then as a primary factor. Once adoption reaches a threshold, offer passwordless login. GitHub, Google, Microsoft, and Apple all followed this incremental path.

Key takeaways

This lesson establishes:

  • Why passwords are fundamentally vulnerable to phishing, reuse, and breach attacks
  • The three actors in FIDO2 (relying party, client, authenticator) and their roles
  • How the WebAuthn registration and authentication flows proceed
  • Why origin binding makes FIDO2 phishing-resistant
  • How resident credentials (passkeys) differ from non-resident credentials, and the trade-offs of synced passkeys

Next: Zero-Knowledge Proofs

← Applied Cryptography WebAuthn and FIDO2