← All modules
Protocols·22 min

Passkeys & WebAuthn

A passkey is what you get when you replace a password with a public-key challenge-response protocol. It fixes reuse, phishing, and breaches in one move.

Developer / EngineerSecurity ArchitectIT Ops / DevOpsCurious Explorer

Why passwords fail, structurally

Passwords have three chronic weaknesses, and all three trace back to the same root cause: a password is a shared secret, and both the user and the server have to know it. Reuse happens because nobody can remember a unique password per site, so one breach unlocks many accounts. Phishing works because a password carries no built-in notion of which site it belongs to — a convincing fake login page can extract it just as easily as the real one. And breaches matter because the server has to store something to check a password against; when that database leaks, the stored hashes (covered in the key-derivation-functions module) become an offline cracking target.

A passkey replaces the shared secret with a key pair. The private key is generated on, and never leaves, the user's device (a phone, laptop, or hardware key); the server only ever receives and stores the matching public key. The server literally has nothing left worth stealing — a leaked passkey database reveals only public keys, which are already public by design.

Password

  • •A shared secret — both user and server must know it
  • •The server stores a hash worth cracking if the database leaks
  • •Carries no notion of which site it belongs to — a phishing page can capture it directly

Passkey

  • •A private key the user keeps, and a public key the server keeps
  • •The server stores only a public key — nothing worth stealing
  • •Automatically scoped to one exact origin by the browser — a phishing site can't even request the right key

Registration: creating a passkey

Signing up for a passkey is a textbook public-key generation step, run inside secure hardware rather than in software. The site's server sends a random challenge and its own identity (the relying party ID — essentially its domain). The user's authenticator — a Secure Enclave, a TPM, or a dedicated hardware key — generates a fresh key pair specifically for that site, signs the challenge with the new private key to prove the hardware genuinely produced it, and returns the public key plus that signature. The server verifies the signature and stores the public key, tied to that user's account.

Passkey registration

  1. 1

    Server sends a challenge

    A fresh random value, plus the site's relying party ID (its domain).

  2. 2

    Authenticator generates a key pair

    A brand-new private/public key pair, generated inside secure hardware and scoped to this one site.

  3. 3

    Authenticator signs the challenge

    Proving this specific hardware, unlocked by the user's biometric or PIN, produced the new key pair.

  4. 4

    Server stores the public key

    Tied to the user's account. The private key never left the device and was never transmitted.

Login: proving possession without revealing anything

Authentication is a direct application of the challenge-response identification pattern that underlies the Schnorr signature scheme covered in the ElGamal module: the server sends a fresh random challenge, the device signs it with the stored private key (after the user unlocks that key locally with a biometric or PIN), and the server verifies the signature using the public key it already has on file. Nothing secret ever crosses the network in either direction — not in registration, and not here.

A fresh challenge every time is what turns this into proof of live possession rather than a replayable secret: signing yesterday's challenge proves nothing about today's login attempt, unlike a password, which is exactly as useful to an attacker on its hundredth use as its first.

sign: σ=Sign(privateKey,challenge)verify: Verify(publicKey,challenge,σ)=?true\text{sign: } \sigma = \mathrm{Sign}(\text{privateKey}, \text{challenge}) \qquad \text{verify: } \mathrm{Verify}(\text{publicKey}, \text{challenge}, \sigma) \stackrel{?}{=} \text{true}

Why this defeats phishing specifically

Passwords and even one-time codes both fail against phishing because a human has to judge whether a login page is legitimate — and attackers are very good at making that judgment fail. WebAuthn removes the human from that specific decision: the browser itself binds every key pair to the exact origin (scheme + domain + port) it was created on, and will only ever offer a key to the origin that owns it. A phishing site at a lookalike domain isn't shown a slightly-suspicious login form the user might click through anyway — it's never even offered the credential to begin with, because the browser's own origin check fails before any signing happens.

This is the single biggest practical difference between passkeys and SMS or app-based one-time codes: an OTP is just another shared secret the user can be tricked into typing into the wrong site, while a passkey's origin binding makes that specific trick structurally impossible, not just less likely.

Device-bound vs. synced passkeys

Not every passkey has an identical trust model. A device-bound passkey never leaves the secure hardware it was created on — maximally resistant to extraction, but unusable if that device is lost, with no backup. A synced passkey backs up an encrypted copy to the user's cloud keychain (iCloud Keychain, Google Password Manager), so it's available on a new device automatically — at the cost of trusting that cloud provider's own encryption and account-recovery process as part of the overall security model.

Device-bound

  • •Private key never leaves the secure hardware it was generated on
  • •Maximum resistance to extraction, even by the device's own OS
  • •Lost device with no backup means the passkey is permanently gone

Synced

  • •An encrypted copy backs up to the user's cloud keychain
  • •Automatically available on a new device, no re-registration needed
  • •Adds the cloud provider's encryption and account-recovery flow to the trust model
Go deeperReading the authenticator's response, byte by byte⌄

Every WebAuthn assertion includes a flags byte in its authenticator data, packing several yes/no facts about the login into individual bits — the same bitwise-flag pattern that shows up throughout networking and protocol design. Bit 0 is User Present (UP): did a human interact with the authenticator at all, even just a touch? Bit 2 is User Verified (UV): did that interaction include an actual biometric or PIN check, not just a touch? Bit 6 is Attested credential data included (AT), set only during registration, not login.

Worked example: a registration response carries the flags byte 0x45 = 01000101 in binary. Bit 0 = 1 (UP: user present), bit 2 = 1 (UV: user verified via biometric/PIN), bit 6 = 1 (AT: this is a registration, carrying new credential data) — every other bit is 0. A server reading this byte confirms, without parsing anything else, that a verified human registered a new credential just now.

flags=(AT≪6)  ∣  (UV≪2)  ∣  (UP≪0)\text{flags} = (\text{AT} \ll 6) \;|\; (\text{UV} \ll 2) \;|\; (\text{UP} \ll 0)

Practice

A login (not registration) assertion has User Present (bit 0) and User Verified (bit 2) both set to 1, with every other flag bit — including Attested credential data (bit 6) — set to 0. What is the flags byte's value in decimal?

Knowledge check

Test what you just learned →

10 quick questions, with an explanation for every answer.

Up next

SSH: key exchange, host keys, and authentication →

The protocol behind every remote login and git push combines the same primitives as TLS, arranged slightly differently.