Federated login and passkeys
Add passkeys with WebAuthn
Understand registration and authentication ceremonies, server challenges, relying-party scope, public-key storage, and recovery planning.
A passkey is a public-key credential built on the WebAuthn browser API. The user’s device, the authenticator, keeps a private key and never gives it out. Your server stores the matching public key and a credential ID. No shared secret crosses the wire, so there’s nothing to phish or leak. I explain the browser side in Passkeys and WebAuthn explained; here we focus on the server.
Two ceremonies
WebAuthn calls each interaction a ceremony. There are two, and they’re separate.
Registration. The server creates options with a fresh random challenge and its relying-party ID, your domain, books.flaviocopes.com for instance. The authenticator creates a key pair scoped to that relying party. The server verifies the response with a maintained WebAuthn library, then stores the credential ID, the public key, and the library’s metadata.
Authentication. The server issues another challenge. The authenticator signs it together with origin-bound data. The server verifies the signature with the stored public key.
The library checks origin, relying-party ID, challenge, signature, and flags. Don’t hand-roll any of that. Both ceremonies need HTTPS in production.
Challenges are single-use and bound
Store each challenge for a couple of minutes and consume it once. Bind it to:
- the session or account that requested it
- the ceremony type, registration or authentication
- the expected origin and relying-party ID
- the user-verification policy you require
Now a registration response can’t satisfy an authentication challenge, and a challenge started in one browser session can’t be redeemed from another. Check too that the returned credential and user handle belong to the account you expect.
Check user presence (someone touched the device) and user verification (they confirmed with biometrics or a PIN) according to your policy. Let the library handle the signature counter and backup-state flags. Synced passkeys often don’t increment counters at all.
Phishing resistance, and what it doesn’t solve
Passkeys resist phishing because the credential is scoped to the relying-party ID. A fake site at books-flaviocopes.com gets a different key or no key at all.
What passkeys don’t remove is lifecycle work. People change phones, switch sync providers, lose devices. Let users register more than one credential, name them, and remove them. Plan a recovery path for the person who lost everything.
Enrolling needs recent authentication
Adding a passkey to an existing account must require fresh proof. Otherwise a stolen session cookie becomes a permanent login method: the thief adds their own passkey and keeps access after the session expires.
Try this: implement both ceremonies on the Books API, then test sign-in, a replayed challenge, a challenge swapped between two sessions, a registration response sent to the authentication endpoint, a wrong origin, a wrong relying-party ID, credential removal, and enrolling through a session that authenticated an hour ago. Only the first may succeed.
Lesson completed