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.
What WebAuthn actually does
Section titled “What WebAuthn actually does”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.
Passkeys vs “classic” WebAuthn
Section titled “Passkeys vs “classic” WebAuthn”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 vs roaming authenticators
Section titled “Platform vs roaming authenticators”- 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.
Conditional UI (passkey autofill)
Section titled “Conditional UI (passkey autofill)”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".
Browser & ecosystem support
Section titled “Browser & ecosystem support”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.
A PWA’s responsibilities
Section titled “A PWA’s responsibilities”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.
Practical checklist
Section titled “Practical checklist”- Feature-detect
window.PublicKeyCredentialbefore showing any passkey UI. - Use
isUserVerifyingPlatformAuthenticatorAvailable()to gate the platform-passkey path. - Set
residentKey: "required"anduserVerification: "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.