Why WebAuthn?
WebAuthn (Web Authentication) is a W3C standard — part of the FIDO2 framework — that enables websites to authenticate users using public-key cryptography instead of passwords. Users authenticate with a passkey: a hardware security key (like a YubiKey), a platform authenticator (Face ID, Touch ID, Windows Hello), or a synced passkey from a password manager.
The security benefits are substantial: WebAuthn credentials are phishing-resistant by design (bound to the origin), never transmitted over the network, and not stored in a central database that can be breached.
Core Concepts Before You Code
- Relying Party (RP): Your web application (the server). Identified by
rpId— typically your domain. - Authenticator: The device or software that generates and stores the key pair (phone, YubiKey, etc.).
- Credential: A public/private key pair. The private key never leaves the authenticator; the public key is stored on your server.
- Challenge: A random nonce generated by your server for each ceremony to prevent replay attacks.
The Two Ceremonies
WebAuthn has two distinct flows — registration (creating a credential) and authentication (using it to prove identity).
Step 1: Registration Flow
Server: Generate Registration Options
Your server generates a challenge and sends registration options to the browser:
// Using the @simplewebauthn/server library (Node.js)
import { generateRegistrationOptions } from '@simplewebauthn/server';
const options = await generateRegistrationOptions({
rpName: 'My App',
rpID: 'myapp.com',
userID: user.id,
userName: user.email,
attestationType: 'none',
authenticatorSelection: {
residentKey: 'preferred',
userVerification: 'preferred',
},
});
// Store options.challenge in the user's session, then send options to client
Browser: Create the Credential
// Using @simplewebauthn/browser
import { startRegistration } from '@simplewebauthn/browser';
const attResp = await startRegistration(optionsFromServer);
// Send attResp to your server for verification
Server: Verify and Store
import { verifyRegistrationResponse } from '@simplewebauthn/server';
const verification = await verifyRegistrationResponse({
response: attResp,
expectedChallenge: session.challenge,
expectedOrigin: 'https://myapp.com',
expectedRPID: 'myapp.com',
});
if (verification.verified) {
// Store verification.registrationInfo.credentialPublicKey
// and verification.registrationInfo.credentialID in your DB
}
Step 2: Authentication Flow
Server: Generate Authentication Options
import { generateAuthenticationOptions } from '@simplewebauthn/server';
const options = await generateAuthenticationOptions({
rpID: 'myapp.com',
allowCredentials: user.credentials.map(cred => ({
id: cred.credentialID,
type: 'public-key',
})),
userVerification: 'preferred',
});
// Store options.challenge in session
Browser: Authenticate
import { startAuthentication } from '@simplewebauthn/browser';
const authResp = await startAuthentication(optionsFromServer);
// Send authResp to server
Server: Verify the Assertion
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
const verification = await verifyAuthenticationResponse({
response: authResp,
expectedChallenge: session.challenge,
expectedOrigin: 'https://myapp.com',
expectedRPID: 'myapp.com',
authenticator: {
credentialPublicKey: storedCredential.publicKey,
credentialID: storedCredential.id,
counter: storedCredential.counter,
},
});
if (verification.verified) {
// Update stored counter, create user session
}
Practical Implementation Tips
- Always update the counter: The authenticator counter prevents credential cloning — store it and reject authentications with a lower-or-equal counter.
- Support multiple credentials per user: Users may want to register multiple devices (phone + YubiKey + laptop).
- Provide a fallback: Not all users will have compatible authenticators. Keep a fallback login method during the transition to passkeys.
- Test with Chrome DevTools: Chrome's DevTools allow you to emulate virtual authenticators — invaluable for testing without physical hardware.
- Use HTTPS: WebAuthn is strictly HTTPS-only (plus
localhostfor development).
Browser Support
WebAuthn is supported in all modern browsers: Chrome, Firefox, Safari, and Edge all have solid implementations. Platform authenticators (passkeys synced via iCloud Keychain, Google Password Manager) make the experience seamless for most users without requiring any additional hardware.
WebAuthn is no longer a niche technology — it's the present and future of secure web authentication.