Passkeys are now offered by Google, Apple, Microsoft, Amazon, GitHub and plenty of banks. If you build login flows, it's worth knowing what's really going on underneath, because the security model is very different from passwords. Here's the short developer version.
A passkey is a key pair, not a stored password
The FIDO Alliance describes a passkey as an authentication credential based on FIDO standards that the user unlocks the same way they unlock their device (biometrics, PIN or pattern).
Under the hood it's a public/private key pair scoped to one relying party (your domain):
- The private key stays in the authenticator: the phone, laptop, password manager or a hardware security key.
- The public key is sent to your server at registration and stored with the account.
Two specs make it work: WebAuthn (the browser API your front end calls) and CTAP (how the browser talks to authenticators). Together they're FIDO2.
The sign-in ceremony
- Your server generates a random challenge and sends it to the browser.
- The browser asks the authenticator, which asks the user to unlock (face, fingerprint, PIN).
- The authenticator signs the challenge with the private key. The key never leaves the device.
- Your server verifies the signature with the stored public key.
On the front end, step 2 is basically one call:
const assertion = await navigator.credentials.get({
publicKey: {
challenge, // random bytes from your server
rpId: "example.com", // your domain
userVerification: "preferred",
},
});
// send assertion to the server for verification
In production, use a maintained server library to verify the response instead of hand-rolling the crypto checks.
Why this kills phishing and credential stuffing
- Nothing to phish. The credential is bound to your real domain, so the browser won't offer it on a look-alike site.
- No replay. Every challenge is random, so a captured signature is useless later.
- Breaches leak nothing useful. Your database only holds public keys.
- No reuse. Each passkey is unique per site.
The FIDO Alliance argues a passkey alone is more secure than "password + OTP" or "password + phone approval". Biometric checks stay on the device; your server only learns that verification succeeded.
Synced vs device-bound
| Synced passkeys | Device-bound passkeys | |
|---|---|---|
| Lives in | iCloud Keychain, Google Password Manager, 1Password, Dashlane… | One authenticator, usually a hardware key |
| New phone | Restored after signing in to the provider | Need the key, or register a new passkey |
| Good for | Most consumer accounts | High-assurance / compliance |
Design your account settings so users can register more than one passkey and keep a recovery path. That's the part that bites teams in support tickets.
Cross-device sign-in
If the passkey isn't on the current machine, the browser can show a QR code. The user scans it with their phone, approves, and the phone acts as the authenticator. Bluetooth Low Energy is only used to prove the phone is physically nearby; the data itself has its own encryption.
Quick checklist for adding passkeys
- Add "Create a passkey" in security settings first, keep passwords as fallback.
- Allow multiple passkeys per account and show them in a list users can revoke.
- Use conditional UI (autofill) so returning users see their passkey in the username field.
- Once adoption is solid, consider dropping SMS as a fallback.
I wrote a plainer, non-developer explainer with diagrams of the flow on my site: the original article is linked as the canonical source above.
Sources: FIDO Alliance: Passkeys, W3C WebAuthn spec.
Top comments (0)