In 2025, stolen credentials were the initial attack vector in 49% of all data breaches (Verizon DBIR 2025). Despite decades of advice about strong passwords, password managers, and multi-factor authentication, the fundamental problem remains: passwords are a shared secret between you and the server, and shared secrets get stolen. Phishing attacks, credential stuffing, database breaches, and keyloggers all exploit this architectural flaw.
Passkeys eliminate this problem entirely. Based on the FIDO2/WebAuthn standard, passkeys use public-key cryptography: a private key stays on your device (protected by biometrics or a PIN), and only the public key is stored on the server. There is no shared secret to steal. Even if an attacker compromises the server's database, the public keys are useless without the corresponding private keys locked in users' devices.
As of 2026, passkeys are supported by all major browsers (Chrome, Safari, Firefox, Edge), all major operating systems (iOS 16+, Android 14+, macOS Ventura+, Windows 10+), and major identity providers (Google, Apple, Microsoft). Over 2 billion Google accounts now support passkeys. This is no longer a future technology — it's ready for production today.
How Passkeys Work: The Technical Flow
Passkey authentication involves two ceremonies: registration (creating a passkey) and authentication (using a passkey to sign in).
Registration Flow
1. The server generates a random challenge and sends it to the browser along with the relying party (your domain) information and user details.
2. The browser calls navigator.credentials.create(), which triggers the platform authenticator (Touch ID, Face ID, Windows Hello, or a security key).
3. The user approves with biometrics or PIN.
4. The authenticator generates a new key pair, stores the private key locally, and returns the public key, credential ID, and a signed attestation to the server.
5. The server stores the public key and credential ID associated with the user.
Authentication Flow
1. The server generates a random challenge and sends it along with the credential IDs registered for this user.
2. The browser calls navigator.credentials.get(), which triggers the authenticator.
3. The user approves with biometrics or PIN.
4. The authenticator signs the challenge with the private key and returns the signature.
5. The server verifies the signature using the stored public key. If valid, the user is authenticated.
Notice what's not transmitted: no password, no shared secret, no OTP code. The private key never leaves the device. Phishing attacks are impossible because the authenticator checks the origin (domain) — it won't sign a challenge from faceb00k.com with credentials registered on facebook.com.
Server-Side Implementation (Node.js)
// Using @simplewebauthn/server (the most popular WebAuthn library)
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse,
} from '@simplewebauthn/server';
const rpName = 'ZeonEdge';
const rpID = 'zeonedge.com';
const origin = 'https://zeonedge.com';
// --- Registration ---
app.post('/api/auth/passkey/register/options', async (req, res) => {
const user = await getUser(req.session.userId);
const existingCredentials = await getUserCredentials(user.id);
const options = await generateRegistrationOptions({
rpName,
rpID,
userID: user.id,
userName: user.email,
userDisplayName: user.name,
attestationType: 'none', // 'none' is fine for most apps
excludeCredentials: existingCredentials.map(c => ({
id: c.credentialID,
type: 'public-key',
transports: c.transports,
})),
authenticatorSelection: {
residentKey: 'preferred', // Enables discoverable credentials
userVerification: 'preferred', // Biometric/PIN when available
},
});
// Store challenge in session for verification
req.session.currentChallenge = options.challenge;
res.json(options);
});
app.post('/api/auth/passkey/register/verify', async (req, res) => {
const expectedChallenge = req.session.currentChallenge;
const verification = await verifyRegistrationResponse({
response: req.body,
expectedChallenge,
expectedOrigin: origin,
expectedRPID: rpID,
});
if (verification.verified && verification.registrationInfo) {
const { credentialPublicKey, credentialID, counter } = verification.registrationInfo;
await saveCredential({
userId: req.session.userId,
credentialID: Buffer.from(credentialID),
credentialPublicKey: Buffer.from(credentialPublicKey),
counter,
transports: req.body.response.transports,
});
res.json({ verified: true });
}
});
Client-Side Implementation (Browser)
// Using @simplewebauthn/browser
import {
startRegistration,
startAuthentication,
} from '@simplewebauthn/browser';
// Register a new passkey
async function registerPasskey() {
// 1. Get options from server
const optionsRes = await fetch('/api/auth/passkey/register/options', {
method: 'POST',
});
const options = await optionsRes.json();
// 2. Create credential (triggers biometric prompt)
const registration = await startRegistration(options);
// 3. Send to server for verification
const verifyRes = await fetch('/api/auth/passkey/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(registration),
});
const result = await verifyRes.json();
if (result.verified) {
alert('Passkey registered successfully!');
}
}
// Sign in with passkey
async function signInWithPasskey() {
const optionsRes = await fetch('/api/auth/passkey/login/options', {
method: 'POST',
});
const options = await optionsRes.json();
const authentication = await startAuthentication(options);
const verifyRes = await fetch('/api/auth/passkey/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(authentication),
});
const result = await verifyRes.json();
if (result.verified) {
window.location.href = '/dashboard';
}
}
Migration Strategy: Passwords to Passkeys
You can't switch to passkeys overnight — not all users have compatible devices, and forcing a migration creates friction. Here's the phased approach we recommend:
Phase 1 (Month 1-2): Add passkey registration as an optional enhancement. After users log in with their password, prompt them: "Want to sign in faster next time? Set up a passkey." Show this on the settings page and as a post-login banner.
Phase 2 (Month 3-6): Make passkeys the default login option. Show the passkey prompt first, with "Use password instead" as a fallback link. Track the percentage of logins using passkeys.
Phase 3 (Month 6-12): When passkey adoption exceeds 80%, start requiring passkeys for sensitive operations (changing email, accessing billing, API key management) even if the user logged in with a password.
Phase 4 (Year 2): For new accounts, make passkeys the primary authentication method. Passwords become the fallback, not the default.
Enterprise Considerations
Account recovery: If a user loses their device, they lose their passkeys. You need a recovery flow: email-based recovery codes, backup passkeys on a secondary device, or administrator-initiated account recovery. Generate and securely store recovery codes during passkey registration.
Cross-device sync: Apple syncs passkeys via iCloud Keychain. Google syncs via Google Password Manager. This means a passkey created on an iPhone is available on a Mac. However, passkeys don't sync across ecosystems — an Apple passkey isn't available on an Android device. Users who switch ecosystems need to register new passkeys.
Compliance: NIST SP 800-63-4 (Digital Identity Guidelines, updated 2024) recognizes WebAuthn/FIDO2 as a phishing-resistant authenticator at AAL2 and AAL3. This satisfies MFA requirements for FedRAMP, HIPAA, PCI DSS, and most compliance frameworks — a single passkey authentication is considered multi-factor because it combines possession (the device) with biometrics or knowledge (the PIN).
ZeonEdge helps organizations implement passwordless authentication with passkeys, including server-side infrastructure, client-side integration, and migration strategy. Contact our security team to get started.
Sarah Chen
Senior Cybersecurity Engineer with 12+ years of experience in penetration testing and security architecture.