Passkeys Explained: How WebAuthn Is Replacing Passwords
Passkeys use FIDO2/WebAuthn public-key cryptography to eliminate passwords entirely. Learn how they work, how to implement them, and how to handle device loss with synced passkeys.
Infrastructure engineer with 10+ years building production systems on AWS, GCP,…

Passwords Are the Problem
Every major data breach in the past decade shares a common thread: stolen credentials. Phishing attacks trick users into handing over passwords. Credential stuffing replays leaked passwords across services. Even "strong" passwords end up in plaintext database dumps because some developer forgot to hash them. The entire security model of passwords -- a shared secret transmitted to the server -- is architecturally broken. Passkeys, built on the FIDO2/WebAuthn standard, replace this model with public-key cryptography. The private key never leaves your device. There is no shared secret to steal. This is not incremental improvement; it is a fundamentally different approach to authentication.
Google, Apple, and Microsoft have all committed to passkeys as the primary authentication method across their platforms. As of 2026, passkeys are supported natively in Chrome, Safari, Firefox, iOS, Android, Windows, and macOS. If you build web applications, this is the standard you need to understand and implement.
What Are Passkeys?
Definition: Passkeys are a FIDO2-based authentication credential that uses public-key cryptography to verify a user's identity. During registration, the device generates a key pair. The public key is sent to the server; the private key stays on the device, protected by biometrics or a device PIN. During authentication, the device signs a server-issued challenge with the private key. The server verifies the signature with the stored public key. No password is ever transmitted or stored.
The FIDO2 standard consists of two components: the WebAuthn API (a W3C specification that browsers implement) and the CTAP2 protocol (Client to Authenticator Protocol, which handles communication between the browser and external authenticators like security keys). Together, they enable passwordless authentication that is phishing-resistant by design -- the credential is cryptographically bound to the origin (domain), so it cannot be replayed on a phishing site.
How Public-Key Authentication Works
Traditional password authentication sends a secret to the server. If the server is compromised, the attacker gets the secret. Passkeys invert this model:
- Registration: The user's device generates an asymmetric key pair (typically ECDSA P-256 or Ed25519). The private key is stored in a secure enclave (TPM, Secure Enclave, or Android Keystore). The public key is sent to the server along with a credential ID.
- Authentication: The server sends a random challenge. The device signs the challenge with the private key (after user verification via biometrics or PIN). The server verifies the signature using the stored public key.
Even if the server's database is fully compromised, the attacker gets only public keys -- which are useless for impersonation. There is no password hash to crack, no credential to replay. The authentication is also bound to the specific origin (e.g., https://example.com), which means a phishing domain like https://examp1e.com cannot trigger the credential. This is phishing resistance at the protocol level, not the user-awareness level.
Platform Adoption: Google, Apple, and Microsoft
Passkeys gained mainstream traction because the three major platform vendors coordinated their rollout:
- Apple: Passkeys sync across iCloud Keychain. Available on iOS 16+, macOS Ventura+, and Safari. The private key is stored in the Secure Enclave and synced end-to-end encrypted across Apple devices.
- Google: Passkeys sync via Google Password Manager. Available on Android 9+ and Chrome. Cross-device authentication allows an Android phone to act as an authenticator for a desktop browser via Bluetooth proximity.
- Microsoft: Windows Hello supports passkeys natively on Windows 10/11. Syncing via Microsoft accounts. Edge supports the full WebAuthn flow.
This cross-platform support means most users already have passkey capability on their devices without installing anything. The adoption barrier is now on the server side, not the client side.
Implementing WebAuthn: Registration
The WebAuthn API is built into modern browsers via navigator.credentials.create() for registration and navigator.credentials.get() for authentication. Here is a complete registration flow.
Client-Side: Creating a Credential
// 1. Fetch registration options from your server
const response = await fetch('/api/auth/register/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'alice@example.com' }),
});
const options = await response.json();
// 2. Create the credential via WebAuthn API
const credential = await navigator.credentials.create({
publicKey: {
// Random challenge from the server (ArrayBuffer)
challenge: base64ToBuffer(options.challenge),
// Your site identity
rp: {
name: 'My App',
id: 'example.com', // Must match the current origin
},
// User information
user: {
id: base64ToBuffer(options.userId),
name: 'alice@example.com',
displayName: 'Alice',
},
// Supported algorithms -- prefer ES256 (ECDSA P-256)
pubKeyCredParams: [
{ alg: -7, type: 'public-key' }, // ES256
{ alg: -257, type: 'public-key' }, // RS256 fallback
],
// Require device-bound user verification (biometric/PIN)
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required',
},
// Timeout in milliseconds
timeout: 60000,
// Prevent duplicate registrations
excludeCredentials: options.existingCredentials.map(cred => ({
id: base64ToBuffer(cred.id),
type: 'public-key',
})),
},
});
// 3. Send the credential to the server for storage
await fetch('/api/auth/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: credential.id,
rawId: bufferToBase64(credential.rawId),
response: {
attestationObject: bufferToBase64(
credential.response.attestationObject
),
clientDataJSON: bufferToBase64(
credential.response.clientDataJSON
),
},
type: credential.type,
}),
});
The browser handles all the cryptography. Your code never touches the private key -- it is generated and stored by the platform authenticator. The attestationObject contains the public key and credential metadata that the server needs to store.
Server-Side: Verifying Registration
On the server, you need to parse the attestation, extract the public key, and store the credential. Libraries like @simplewebauthn/server (Node.js) and py_webauthn (Python) handle the heavy lifting:
// Node.js with @simplewebauthn/server
import {
generateRegistrationOptions,
verifyRegistrationResponse,
} from '@simplewebauthn/server';
// Generate options (called before the client-side code above)
const options = await generateRegistrationOptions({
rpName: 'My App',
rpID: 'example.com',
userID: user.id,
userName: user.email,
attestationType: 'none', // 'none' is fine for most apps
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required',
},
});
// Store the challenge for verification
await storeChallenge(user.id, options.challenge);
// Verify the registration response
const verification = await verifyRegistrationResponse({
response: registrationBody, // From the client POST
expectedChallenge: storedChallenge, // The challenge you stored
expectedOrigin: 'https://example.com',
expectedRPID: 'example.com',
});
if (verification.verified) {
const { credentialID, credentialPublicKey, counter } =
verification.registrationInfo;
// Store in your database
await db.credentials.create({
userId: user.id,
credentialId: credentialID,
publicKey: credentialPublicKey,
counter: counter, // Replay attack protection
transports: registrationBody.response.transports,
});
}
Implementing WebAuthn: Authentication
Authentication follows a similar pattern: the server generates a challenge, the client signs it with the private key, and the server verifies the signature.
// Client-side authentication
const options = await fetch('/api/auth/login/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'alice@example.com' }),
}).then(r => r.json());
const assertion = await navigator.credentials.get({
publicKey: {
challenge: base64ToBuffer(options.challenge),
rpId: 'example.com',
allowCredentials: options.allowCredentials.map(cred => ({
id: base64ToBuffer(cred.id),
type: 'public-key',
transports: cred.transports,
})),
userVerification: 'required',
timeout: 60000,
},
});
// Send assertion to server
await fetch('/api/auth/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: assertion.id,
rawId: bufferToBase64(assertion.rawId),
response: {
authenticatorData: bufferToBase64(
assertion.response.authenticatorData
),
clientDataJSON: bufferToBase64(
assertion.response.clientDataJSON
),
signature: bufferToBase64(assertion.response.signature),
},
type: assertion.type,
}),
});
Pro tip: For the best user experience, use conditional UI (autofill-assisted passkeys). Pass
mediation: 'conditional'tonavigator.credentials.get()and addautocomplete="username webauthn"to your username input. The browser will show passkey suggestions in the autofill dropdown alongside saved passwords, letting users authenticate with a single tap.
Synced Passkeys and Device Loss Recovery
The original FIDO2 vision was device-bound credentials: if you lose the device, you lose the credential. That is secure but creates a terrible recovery experience. Synced passkeys solve this by replicating the private key across a user's devices via their platform account (iCloud Keychain, Google Password Manager, or Microsoft Account).
When a user registers a passkey on their iPhone, it syncs automatically to their iPad and Mac via iCloud Keychain. The sync is end-to-end encrypted -- Apple cannot read the private keys. If the user loses all their devices, they recover access through their Apple/Google/Microsoft account recovery process.
This tradeoff is deliberate: synced passkeys are slightly less secure than device-bound credentials (the key exists on multiple devices), but massively more usable. For most consumer applications, synced passkeys are the right choice. For high-security environments, device-bound credentials on hardware security keys remain available.
| Aspect | Synced Passkeys | Device-Bound Passkeys |
|---|---|---|
| Key storage | Platform cloud (encrypted) | Single device only |
| Recovery | Via platform account | Must register backup credential |
| Use case | Consumer apps | High-security / enterprise |
| Hardware examples | iPhone, Android, Windows Hello | YubiKey, Titan Key |
| Phishing resistant | Yes | Yes |
Supporting Passkeys Alongside Passwords
Most applications cannot go passkey-only overnight. You need a migration strategy that supports both authentication methods during the transition. Here is a practical approach:
- Add passkey registration to account settings. Let existing users add a passkey to their account without removing their password. This is a non-disruptive first step.
- Offer passkey at login with fallback. Show a "Sign in with passkey" option prominently, but keep password login available. Use conditional UI so passkey suggestions appear automatically in the username field.
- Track adoption metrics. Monitor how many users register passkeys and how many authentications use passkeys vs. passwords. This data drives your migration timeline.
- Encourage passkey registration post-login. After a user logs in with a password, prompt them to register a passkey. A simple banner or modal with a clear value proposition ("Sign in faster, no password needed") drives conversion.
- Eventually deprecate passwords for passkey users. Once a user has registered passkeys on their primary devices, you can offer to disable password login entirely. Never force this -- let users opt in.
Enterprise: FIDO2 Security Keys and SSO Integration
In enterprise environments, passkeys integrate with existing identity infrastructure. Hardware security keys like YubiKey 5 or Google Titan Keys provide device-bound FIDO2 credentials that satisfy the strictest compliance requirements (NIST SP 800-63B AAL3).
For SSO integration, passkeys work at the identity provider (IdP) level. Configure your IdP (Okta, Entra ID, Ping Identity) to accept FIDO2 as an authentication factor, and every application behind that IdP benefits from passkey authentication without individual integration. This is the most practical approach for organizations with dozens or hundreds of applications.
Key enterprise considerations:
- FIDO2 as MFA factor: Use passkeys as the "something you have" factor in MFA. Combined with the biometric unlock ("something you are"), a single passkey gesture satisfies two factors in one step.
- Conditional access policies: Require passkeys for high-risk operations (admin actions, financial transactions) while allowing weaker methods for low-risk activities.
- Attestation: Enterprise deployments can require attestation to verify that credentials come from approved authenticator models (e.g., only YubiKey 5 series). This is configured via the
attestationparameter in registration options. - Account recovery at scale: Pre-register backup security keys for each user. Maintain a help-desk process for users who lose all authenticators.
Frequently Asked Questions
What happens if I lose my phone?
If you use synced passkeys (the default on iOS and Android), your passkeys are backed up to iCloud Keychain or Google Password Manager. When you set up a new phone and sign in to your Apple or Google account, your passkeys sync automatically. If you lose all devices, you recover through your platform account's recovery process. For critical accounts, register a backup hardware security key and store it securely.
Are passkeys phishing-resistant?
Yes. Passkeys are bound to the exact origin (domain) where they were created. If you registered a passkey on https://bank.com, a phishing site at https://bank-secure.com cannot trigger that credential. The browser enforces this at the protocol level -- no user judgment required. This is the single biggest advantage over passwords and even over SMS/TOTP-based MFA.
Can I use passkeys on a shared or public computer?
Yes, through cross-device authentication. When you encounter a passkey prompt on a shared computer, you can scan a QR code with your phone. The phone acts as the authenticator via a Bluetooth proximity check (to verify you are physically present), and the private key never touches the shared computer. This flow is built into the WebAuthn specification and supported by all major browsers.
Do passkeys replace MFA?
A passkey provides two authentication factors in a single gesture: "something you have" (the device holding the private key) and "something you are" (the biometric that unlocks the device). Many security frameworks, including NIST SP 800-63B, recognize this as multi-factor authentication. However, some compliance regimes may still require a separate second factor. Check your specific requirements.
How do passkeys work with password managers like 1Password or Bitwarden?
Third-party password managers now support storing and syncing passkeys, giving users an alternative to platform-native sync (iCloud/Google). On desktop, the browser presents a choice between the platform authenticator and the password manager. On mobile, the OS credential manager API lets third-party apps act as passkey providers. This is useful for users who work across Apple, Google, and Windows ecosystems and want a single passkey provider.
What is conditional UI for passkeys?
Conditional UI (also called "autofill-assisted" passkeys) lets the browser show passkey options in the standard autofill dropdown of a username input. Instead of a modal dialog that interrupts the user, passkey suggestions appear alongside saved passwords. Users tap their passkey suggestion, verify with biometrics, and they are signed in. Implement it by passing mediation: 'conditional' to navigator.credentials.get() and adding autocomplete="username webauthn" to your input element.
What libraries should I use for server-side WebAuthn?
For Node.js, use @simplewebauthn/server and @simplewebauthn/browser -- they handle CBOR parsing, attestation verification, and challenge management. For Python, use py_webauthn. For Go, use go-webauthn/webauthn. For Ruby, use webauthn-ruby. These libraries abstract the complex CBOR/COSE encoding and signature verification so you can focus on the integration logic.
The Bottom Line
Passkeys are the most significant improvement to authentication security since the invention of password hashing. They eliminate phishing, credential stuffing, and password reuse in one architectural shift. The WebAuthn API is well-supported across browsers and platforms, and libraries like SimpleWebAuthn reduce the server-side implementation to a few function calls. Start by adding passkey registration alongside your existing password flow. Use conditional UI for a seamless login experience. For enterprise deployments, integrate FIDO2 at the IdP level and issue hardware security keys where compliance demands it. The private key never leaves the device. That single fact changes everything.
Written by
Abhishek Patel
Infrastructure engineer with 10+ years building production systems on AWS, GCP, and bare metal. Writes practical guides on cloud architecture, containers, networking, and Linux for developers who want to understand how things actually work under the hood.
Related Articles
Network Firewalls vs WAFs: Understanding Your Defense Layers
Network firewalls filter by IP and port at Layer 3/4. WAFs inspect HTTP content at Layer 7. Learn when you need each and how to configure them together.
10 min read
SecurityCertificate Management at Scale: Let's Encrypt, ACME, and cert-manager
Automate TLS certificates with Let's Encrypt, ACME protocol, and cert-manager in Kubernetes. Covers HTTP-01, DNS-01, wildcards, private CAs, and expiry monitoring.
9 min read
SecuritySSRF Attacks: What They Are and Why Cloud Environments Make Them Dangerous
SSRF lets attackers reach internal services through your server. Learn how cloud metadata endpoints amplify the risk and how to defend against SSRF.
9 min read
Enjoyed this article?
Get more like this in your inbox. No spam, unsubscribe anytime.