
Passkeys and WebAuthn: The Era of Passwordless Authentication
Password resets were half my support tickets. Passkeys eliminate passwords entirely, but implementation is more complex than expected.

Password resets were half my support tickets. Passkeys eliminate passwords entirely, but implementation is more complex than expected.
A comprehensive deep dive into client-side storage. From Cookies to IndexedDB and the Cache API. We explore security best practices for JWT storage (XSS vs CSRF), performance implications of synchronous APIs, and how to build offline-first applications using Service Workers.

App crashes only in Release mode? It's likely ProGuard/R8. Learn how to debug obfuscated stack traces, use `@Keep` annotations, and analyze `usage.txt`.

Bitcoin is just part of it. How to create trust without a central authority, principles of decentralization, smart contracts, gas fees, Layer 2 solutions, DAOs, and the Oracle Problem.

Why your server isn't hacked. From 'Packet Filtering' checking ports/IPs to AWS Security Groups. Evolution of Firewalls.

Half of my customer support tickets were "I forgot my password." At first, I thought this was normal. Everyone forgets passwords. But the problem didn't stop there.
Users reused the same password across multiple sites, and when one site had a data breach, our service became vulnerable too. When we enforced stronger password complexity policies, users forgot their passwords even more frequently. When we introduced 2FA, complaints about "login is too cumbersome" flooded in.
This was the fundamental limitation of passwords. If they're complex enough to be secure, they're hard to remember. If they're easy to remember, they're vulnerable. While searching for a solution to this dilemma, I discovered Passkeys.
At first, I thought it was "just another authentication method." But once I understood it, it was completely different. This wasn't about improving passwords—it was about eliminating passwords entirely.
The core idea of Passkeys was so simple it was surprising: "Don't store secrets on the server."
Traditional password systems work like this: users enter a password, and it's compared against a hash stored on the server. The server must store something in the database. This is where the problem begins. If the database gets hacked, everything is compromised.
Passkeys work in the opposite way. The secret key is stored only on the user's device (smartphone, laptop), and the server only holds the public key. This is like the relationship between a lock and a key.
Imagine creating a safe deposit box at a bank. The traditional password approach is like telling the bank employee your safe's password. Every time you visit, you tell them the password, they verify it, and open the safe. The problem is that the bank employee also knows your password. If the employee is malicious or their notes get stolen, your safe is at risk.
The Passkey approach is like you holding the key while the bank only has the lock. Every time you visit, you unlock it with your key. The bank doesn't know your key. The lock alone can't open the safe. This is the essence of public key cryptography.
Passkeys didn't appear suddenly. They're built on top of WebAuthn, a standard created by W3C and the FIDO Alliance. Without this standard, each company would have created their own authentication system, resulting in compatibility hell.
The genius of WebAuthn is that it's designed as a browser API. Developers just need to call navigator.credentials.create() and navigator.credentials.get(). The browser handles the rest. Whether to use biometrics, security keys, or PIN is decided by the operating system and device.
Think of it like a postal system. Imagine if every neighborhood used a different mail system—red envelopes in neighborhood A, blue envelopes in neighborhood B, and addresses written only in numbers in neighborhood C. Sending mail nationwide would be nearly impossible.
WebAuthn is a nationwide common postal system. All neighborhoods use the same address format and the same way of attaching stamps. That's why you can send mail anywhere. With Apple, Google, and Microsoft all supporting the WebAuthn standard, a Passkey created once works across all platforms.
The Passkey system operates in two stages: Registration and Authentication. When I first implemented it, I confused these two and struggled for quite a while.
Registration Flow: Creating the KeyWhen a user first registers a Passkey:
Here's what it looks like in code:
// Server: Generate registration challenge
import { generateRegistrationOptions } from '@simplewebauthn/server';
const options = await generateRegistrationOptions({
rpName: 'My App',
rpID: 'example.com',
userID: user.id,
userName: user.email,
challenge: randomBytes(32),
authenticatorSelection: {
residentKey: 'required', // Discoverable credential
userVerification: 'preferred',
},
});
// Client: Generate key pair
const credential = await navigator.credentials.create({
publicKey: options
});
// Server: Verify and store public key
const verification = await verifyRegistrationResponse({
response: credential,
expectedChallenge: storedChallenge,
expectedOrigin: 'https://example.com',
});
if (verification.verified) {
await savePublicKey(user.id, verification.registrationInfo);
}
Authentication Flow: Unlocking with the Key
When a user logs in:
// Server: Generate authentication challenge
const options = await generateAuthenticationOptions({
rpID: 'example.com',
challenge: randomBytes(32),
allowCredentials: userCredentials.map(cred => ({
id: cred.credentialID,
type: 'public-key',
})),
});
// Client: Sign with private key
const assertion = await navigator.credentials.get({
publicKey: options
});
// Server: Verify signature
const verification = await verifyAuthenticationResponse({
response: assertion,
expectedChallenge: storedChallenge,
expectedOrigin: 'https://example.com',
authenticator: storedPublicKey,
});
if (verification.verified) {
return createSession(user.id);
}
The most confusing part during implementation was Discoverable Credentials. This is about whether the Passkey includes user information.
Non-Discoverable: The server must tell the client "This user's credential ID is this." The user enters their email first, then the server finds the user's credential list and sends it to the client.
Discoverable (Resident Key): The Passkey itself includes user information. The user doesn't need to enter anything. The device's authenticator (Face ID, Touch ID) shows a list saying "Available Passkeys for this site are these."
With Discoverable, passwords are completely unnecessary. Visit site → Face ID authentication → Done. You don't even enter an email. This is the real future of Passkeys.
The theory was perfect. But when I actually deployed it, problems emerged. Old Android phones, enterprise users insisting on Internet Explorer, Windows 7 laptops—WebAuthn didn't work in these environments.
When I checked browser compatibility:
More widespread than expected, but still, 5-10% of users were on unsupported environments. So I adopted a hybrid strategy.
// Feature detection
async function checkPasskeySupport() {
if (!window.PublicKeyCredential) {
return false;
}
const available = await PublicKeyCredential
.isConditionalMediationAvailable();
return available;
}
// Hybrid authentication UI
function LoginForm() {
const [passkeySupported, setPasskeySupported] = useState(false);
useEffect(() => {
checkPasskeySupport().then(setPasskeySupported);
}, []);
return (
<div>
{passkeySupported ? (
<button onClick={loginWithPasskey}>
Sign in with Passkey
</button>
) : null}
<form onSubmit={loginWithPassword}>
<input type="email" name="email" />
<input type="password" name="password" />
<button type="submit">Sign in with Password</button>
</form>
</div>
);
}
The trickiest part was how to transition existing users. Thousands of users were already logging in with passwords. Suddenly requiring Passkey-only would obviously create backlash.
Optional migration strategy:
I initially tried to implement the WebAuthn spec directly. CBOR encoding, attestation verification, ECDSA signature verification... After 2 weeks of struggling, I gave up.
Discovering the SimpleWebAuthn library was lucky. It abstracted complex cryptographic operations, allowing registration and authentication in just a few lines of code.
The library handles:
Doing these low-level operations manually would have taken a month.
The biggest advantage of Passkeys wasn't security—it was phishing prevention. This was a surprising side effect I discovered accidentally.
Traditional passwords are vulnerable to phishing. Create a fake site (examp1e.com), get users to enter their password, and you're done. Even 2FA can be bypassed with real-time relay attacks.
Passkeys are different. Origin verification is built into the WebAuthn spec. If a user created a Passkey on example.com, that Passkey only works on example.com. It won't work at all on examp1e.com. Because the browser checks the origin, it's technically impossible even if the user is fooled.
There was a moment when I realized how powerful this was. A phishing site impersonating our service was discovered, but not a single user using Passkeys was affected. Login attempts from the fake site were simply impossible.
The real revolution of Passkeys was synchronization. Old security keys (like YubiKey) were physical devices. If you lost it, that was it. Passkeys are stored encrypted in the cloud and synced to all your devices.
What's amazing is that cross-platform works too. You can use a Passkey created on iPhone on Android. Scan a QR code to create a temporary Bluetooth connection for authentication.
I actually tested this. When logging in on my MacBook, I authenticated with my iPhone's Face ID. A QR code appeared, I scanned it with my iPhone camera, authenticated with Face ID, and I was logged in on my MacBook. It felt like magic.
Three months after introducing Passkeys, "password reset" tickets almost disappeared from customer support. Users using Passkeys have a 95% lower login failure rate. No security incidents.
Key realizations:Passwords are now legacy. In 10 years, we'll say "We used to use something called passwords." No need to wait that long. Implement Passkeys right now. Users get more convenience, developers get more security. There's no better deal than that.