Skip to content

WebAuthn and passkeys

Published

In one line: The Web Authentication API (WebAuthn) lets a site register and verify a public-key credential bound to a device’s authenticator, so users sign in with a fingerprint, face, or device PIN instead of a password. A passkey is a discoverable WebAuthn credential that typically syncs across a user’s devices — phishing-resistant by design, because the signature is bound to the site’s origin and the private key is never exposed to the site.

WebAuthn replaces the shared secret (a password) with a key pair. For a passkey (discoverable credential), the private key is held by the authenticator (a secure element, TPM, or platform keystore); for some non-discoverable credentials the private key may instead be stored by the relying party in wrapped form, encrypted under a master key the authenticator holds. Either way the public key is stored by your server and the private key is never handed to the site. There are two ceremonies:

  • Registration (navigator.credentials.create()) — the authenticator generates a new key pair for your origin and returns the public key plus an attestation. You store the public key and a credential ID against the user account.
  • Authentication (navigator.credentials.get()) — the authenticator signs a server-issued challenge with the private key. Your server verifies the signature with the stored public key.

Because the signed data includes the origin and a fresh challenge, a credential phished on a look-alike domain is useless, and a captured assertion can’t be replayed.

A passkey is a WebAuthn credential that is discoverable (a.k.a. resident) — the authenticator stores enough state to find the credential without the server first naming a credential ID. That’s what enables “just tap to sign in” with no username typed first. Combined with provider sync (iCloud Keychain, Google Password Manager, etc.), the same passkey works across a user’s devices.

Classic WebAuthn (second factor) Passkey
Discoverable credential Usually not required Yes (residentKey: "required")
User identifier needed first Often (username + password, then key) No — credential is discoverable
Typical role Second factor on top of a password Primary, passwordless sign-in
Cross-device Roaming security key, per device Synced across the user’s devices
  • Platform authenticator — built into the device (Touch ID / Face ID, Windows Hello, Android biometrics). Request it with authenticatorAttachment: "platform". Best for the primary passkey on a user’s own device.
  • Roaming (cross-platform) authenticator — a removable security key (USB/NFC/Bluetooth) or a phone used to sign in to another device. Use "cross-platform" for hardware keys or cross-device flows.

Use PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() to detect whether a platform authenticator exists before offering a passkey path, and isConditionalMediationAvailable() to enable autofill-style passkey suggestions.

Calling navigator.credentials.get() with mediation: "conditional" lets the browser offer saved passkeys inline in the username field’s autofill, instead of a modal. The request waits silently until the user picks a passkey, so you can start it on page load without interrupting users who’d rather type. Pair the input with autocomplete="username webauthn".

WebAuthn is supported across current major browsers. Passkeys — discoverable credentials with cross-device sync — are available on several major platforms, though coverage is still filling in: the cited passkeys.dev device-support matrix tracks per-platform gaps (for example, synced passkeys are listed as planned rather than shipped on some platforms). Where supported, sync is handled by a platform credential manager such as Google Password Manager or iCloud Keychain rather than the browser alone. Because the matrix shifts, feature-detect (PublicKeyCredential, conditional mediation) rather than assuming.

WebAuthn is only as safe as the server-side verification. The browser API hands you data to check, not a finished decision:

  • Generate a cryptographically random challenge per ceremony and bind it to the session; reject a stale or reused challenge.
  • Verify the origin and RP ID in the returned client data match your site.
  • On registration, store the public key, credential ID, and signature counter; on each authentication, confirm the counter hasn’t gone backwards (a clone signal).
  • Treat passkeys as account credentials: let users enroll more than one, name them, and revoke a lost one.
  • Feature-detect window.PublicKeyCredential before showing any passkey UI.
  • Use isUserVerifyingPlatformAuthenticatorAvailable() to gate the platform-passkey path.
  • Set residentKey: "required" and userVerification: "preferred" for true passkeys.
  • Issue a fresh random challenge server-side and verify origin, RP ID, and signature.
  • Persist and check the signature counter to detect cloned authenticators.
  • Offer conditional-UI autofill with autocomplete="username webauthn" where supported.
  • Allow multiple passkeys per account and a recovery path so a lost device isn’t a lockout.