Passkeys in 2026 — WebAuthn Basics for Web Devs
Passkeys are not “passwords stored in the cloud with better branding.” They are WebAuthn credentials: public-key pairs scoped to your site’s relying party ID, unlocked by local biometrics or device PIN, often synced via platform vendors (iCloud Keychain, Google Password Manager, Windows Hello). In 2026 you can ship them without inventing crypto — but the ceremony and RP ID rules still bite teams who treat them like another OAuth button.
Ceremony in one page
Registration (create)
- User is authenticated (or in a signup flow).
- Server sends
PublicKeyCredentialCreationOptions(challenge,rp.id, user id, algorithms). - Browser / authenticator creates a credential; returns attestation/attested data.
- Server verifies the challenge and stores the credential id + public key.
Authentication (get)
- Server sends
PublicKeyCredentialRequestOptions(challenge, allowed credential ids optional). - Authenticator signs the challenge.
- Server verifies signature with the stored public key and checks challenge/RP ID/origin.
The private key never hits your server. Phishing resistance comes from the browser binding the credential to the RP ID / origin — a lookalike domain cannot complete the ceremony for example.com.
RP ID and origin footguns
// server options (shape)
{
rp: { name: "Acme", id: "example.com" }, // eTLD+1 typically
user: { id: userIdBytes, name: email, displayName },
challenge: randomBytes,
pubKeyCredParams: [{ alg: -7, type: "public-key" }, { alg: -257, type: "public-key" }],
}
| Mistake | Result |
|---|---|
RP ID www.example.com but users also use apex | Credentials do not work across hosts unless carefully designed |
Testing on localhost then shipping different RP ID | Dev credentials useless in prod (expected) — plan separate envs |
| IP address as origin | WebAuthn support is painful; use real hostnames |
| HTTP in production | Secure context required |
Subdomains: credentials registered under RP ID example.com can work for app.example.com if the browser accepts that RP ID relationship. Do not casually change RP ID after launch — you orphan passkeys.
UX gotchas that generate support tickets
- Cross-device login: User registered on iPhone, opens desktop without sync / without QR hybrid flow. Offer discoverable credentials + hybrid transport, and keep a recovery path (email magic link, not “just reset password” if you removed passwords).
- Enterprise browsers / MDM: Security keys required; platform passkeys blocked. Detect failures and show “try security key” copy.
- Autofill conditional UI:
mediation: 'conditional'for passkey autofill in the username field — great when supported, silent no-op in older browsers; feature-detect. - Account recovery: Passkeys can be lost with the Apple ID / Google account. Ship backup codes or second factor enrollment day one.
Server verification essentials
Use a maintained library (@simplewebauthn/server, etc.). Manually parsing CBOR once is a CVE farm. Always:
- Consume challenges (single use, short TTL).
- Verify
originandrpIdHash. - Update signature counters if the authenticator provides them (clone detection signal).
- Store credentials per user; allow multiple passkeys (phone + laptop).
Minimal product checklist
- Feature-detect
PublicKeyCredentialandisUserVerifyingPlatformAuthenticatorAvailable. - Separate RP IDs / origins per environment.
- Multi-device story documented before marketing “passwordless.”
- Fallback auth path tested under “lost phone.”
- Logging for ceremony step failures (without logging raw authenticator blobs into public channels).
Passkeys remove shared secrets from your database. They do not remove product design. Get RP ID and recovery right, and the crypto libraries will do the rest.