Implementing WebAuthn (Passkeys)
A high-level guide to understanding and deploying WebAuthn for robust, device-bound cryptographic proof-of-humanity.
As we established in the 2024 State of Bot Detection, heuristics, telemetry, and visual puzzles are failing. The only mathematically sound method to prove a human authorized an action is through cryptographic attestation tied to secure hardware.
The W3C Web Authentication API (WebAuthn), commonly branded as "Passkeys", is the industry standard for this transition.
The Core Concept
WebAuthn replaces passwords and OTPs with asymmetric cryptography.
- Registration: When a user registers, their device (smartphone, laptop, YubiKey) generates a unique public/private key pair. The private key never leaves the device's secure hardware enclave (TPM or Secure Enclave). The public key is sent to your server.
- Authentication: When the user logs in, your server sends a challenge. The user's device signs the challenge with the private key. Your server verifies the signature using the stored public key.
Crucially, the device will not sign the challenge unless the human explicitly authorizes it via local biometrics (Face ID, Touch ID) or a PIN.
You can simulate this flow using our WebAuthn Attestation Demo.
Why WebAuthn Defeats Bots
WebAuthn effectively destroys the ROI of automated attacks like credential stuffing and phishing.
It is Phishing-Resistant
The cryptographic signature is intrinsically bound to the Relying Party (the domain). If a user is tricked into visiting paypa1.com instead of paypal.com, the device will refuse to sign the challenge because the domain does not match the original registration. The credential cannot be stolen.
It Cannot Be Scaled
An attacker cannot automate millions of logins from a server farm because the private keys do not exist on the server farm. They exist on the victims' physical devices. The attacker would need physical access to the device and the victim's biometric data to authorize the signature.
Implementation Architecture
Implementing WebAuthn requires changes to both the frontend and the backend.
The Frontend (Browser API)
The frontend utilizes navigator.credentials.create() for registration and navigator.credentials.get() for authentication.
// Simplified Registration Request
const credential = await navigator.credentials.create({
publicKey: {
challenge: serverGeneratedChallenge,
rp: { name: "ThisIsAHuman", id: "thisisahuman.com" },
user: { id: userId, name: "[email protected]", displayName: "User" },
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
authenticatorSelection: { userVerification: "required" }
}
});
The Backend (Verification Server)
Your backend must securely generate challenges, parse the complex CBOR-encoded responses from the device, verify the cryptographic signatures, and manage public key storage.
Because the WebAuthn spec is complex and unforgiving, it is highly recommended to use established libraries for your backend language rather than writing the cryptography yourself.
- Node.js:
@simplewebauthn/server - Go:
go-webauthn - Python:
webauthn
The Migration Strategy
Deprecating passwords entirely is difficult. The standard approach is progressive enhancement.
- Offer Passkeys as an Alternative: Allow users to register a passkey alongside their traditional password.
- Incentivize Adoption: Explain the security benefits and the frictionless login experience (no more typing SMS codes).
- Use for Step-Up Authentication: Even if a user logs in with a password, require WebAuthn for high-risk actions (e.g., changing banking details or transferring funds), effectively neutralizing session hijacking.
For a deeper understanding of the hardware involved, read our guide on Device Enclaves and Attestation.