WebAuthn / FIDO2 Overview
Passkeys use public-key cryptography. The private key never leaves the device; the server stores only the public key.
Registration Flow
- Server generates a random challenge and returns it along with
rp(relying party) info. - Browser calls
navigator.credentials.create()with the challenge. - Authenticator generates an asymmetric key pair; private key stored in secure enclave.
- Authenticator returns an attestation object containing the public key, credential ID, and signed challenge.
- Server validates attestation and stores the credential.
// Client-side (simplified)
const credential = await navigator.credentials.create({
publicKey: {
challenge: base64ToArrayBuffer(serverChallenge),
rp: { name: "Example", id: "example.com" },
user: { id: userId, name: userEmail, displayName: userName },
pubKeyCredParams: [{ type: "public-key", alg: -7 }] // ES256
}
});
// POST credential.response to /webauthn/register
Authentication (Assertion) Flow
- Server generates a fresh challenge and returns allowed credential IDs for the user.
- Browser calls
navigator.credentials.get(). - Authenticator signs the challenge with the stored private key.
- Server verifies the signature against the stored public key and checks the sign count.
// Client-side (simplified)
const assertion = await navigator.credentials.get({
publicKey: {
challenge: base64ToArrayBuffer(serverChallenge),
allowCredentials: [{ type: "public-key", id: credentialId }]
}
});
// POST assertion.response to /webauthn/authenticate
Data Model
Credential Table
Credential (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT,
credential_id BYTEA UNIQUE, -- opaque handle from authenticator
public_key BYTEA, -- COSE-encoded public key
aaguid UUID, -- authenticator model identifier
sign_count INT DEFAULT 0, -- replay protection
device_name TEXT, -- user-provided label
created_at TIMESTAMP,
last_used_at TIMESTAMP
)
Challenge Store (Redis)
SET challenge:{session_id} <base64_challenge> EX 300 -- 5-minute TTL
Registration Validation
- Verify attestation statement format and signature.
- Check
originmatches expected RP origin. - Verify
rpIdHashin authenticatorData matches SHA-256 of the RP ID. - Confirm the challenge in clientDataJSON matches the stored challenge.
Assertion Validation
- Re-verify rpIdHash and origin.
- Verify signature over
authenticatorData || SHA-256(clientDataJSON)using stored public key. - Assert
sign_count > stored_sign_countto detect cloned authenticators; update stored value.
Multiple Passkeys and Sync
Users may register multiple credentials (phone + laptop). Synced passkeys are backed up via iCloud Keychain or Google Password Manager, identified by aaguid. Platform authenticators set the BS (backup state) flag in authenticatorData.
Fallback Flow
Users without a registered passkey fall back to password + 2FA. The registration prompt is shown post-login to encourage passkey adoption.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How does passkey registration work with WebAuthn?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The server generates a random challenge and returns it with relying party info. The browser calls navigator.credentials.create(), which triggers the authenticator to generate a public/private key pair. The private key stays on the device in a secure enclave; the public key and credential ID are sent to the server, which validates the attestation and stores the credential.”
}
},
{
“@type”: “Question”,
“name”: “What is sign_count and why is it important in passkey authentication?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The authenticator increments a sign_count with each assertion. The server checks that the received sign_count is strictly greater than the stored value. If it's equal or lower, the server can detect a cloned authenticator and reject or flag the authentication attempt, providing replay attack protection.”
}
},
{
“@type”: “Question”,
“name”: “How are passkeys synced across a user's devices?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Platform authenticators can back up credentials to cloud keychain services such as iCloud Keychain (Apple) or Google Password Manager. The authenticatorData backup state (BS) flag indicates a credential is backed up. Synced passkeys allow seamless use across a user's devices without requiring re-registration on each one.”
}
},
{
“@type”: “Question”,
“name”: “What challenge storage strategy is recommended for WebAuthn?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Challenges should be stored server-side in Redis keyed by session ID with a short TTL (5 minutes). This prevents challenge reuse and replay attacks. The challenge must be at least 16 random bytes, base64url-encoded, and verified consumed (deleted from Redis) immediately after a successful or failed authentication attempt.”
}
}
]
}
See also: Apple Interview Guide 2026: iOS Systems, Hardware-Software Integration, and iCloud Architecture
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering