Implementing Passkeys and WebAuthn for Passwordless Login
Introduction to Passwordless Authentication
Traditional passwords represent one of the largest security liabilities in modern digital architecture. Subject to credential stuffing, phishing, and database leaks, reliance on shared secrets exposes organizations to constant threat.
The Web Authentication (WebAuthn) API offers a standardized, cryptographically secure alternative. By replacing passwords with asymmetric key pairs generated and stored on a user's physical device, WebAuthn establishes a passwordless authentication flow that is inherently resistant to phishing. When implemented correctly, WebAuthn—packaged commercially as Passkeys—creates a seamless sign-in experience while elevating the security posture of enterprise applications to zero-trust standards.
To understand the mechanics of WebAuthn, we must dissect the registration and authentication ceremonies. The process involves three primary entities: the Relying Party (the backend server and application logic), the Client (the user's browser), and the Authenticator (the hardware device, such as a secure enclave chip, a YubiKey, or built-in biometrics like TouchID).
In contrast to password setups, the server never stores or receives a password. Instead, during registration, the authenticator generates a unique public-private key pair.
The private key remains locked within the secure hardware of the device, protected by user verification (like biometrics or a device PIN). Only the public key, along with a newly generated credential ID, is transmitted back to the server for future verification.
The Registration Ceremony and Signature Verification
The registration ceremony begins when the Relying Party (RP) server generates a challenge—a random cryptographically secure byte sequence. This challenge, along with the RP's metadata, is sent to the client.
The browser executes navigator.credentials.create(), passing these options to trigger the local authenticator. The user verifies their identity via biometrics or passcode, prompting the authenticator to generate a key pair and sign the challenge.
The authenticator packages this data into an Attestation Object, which is sent back to the server. The attestation contains the new public key encoded in COSE (CBOR Object Signing and Encryption) format, nested within a CBOR (Concise Binary Object Representation) byte stream.
To verify the registration payload, the RP server must parse the CBOR data. It verifies that the returned challenge matches the original challenge sent, confirming the request was not intercepted or replayed.
The server then validates the signature using the public key and extracts the credential ID. This credential ID and the COSE-formatted public key are saved in the user's database record. Because the registration payload includes attestation statements, the server can also verify the authenticity of the hardware itself, ensuring the key pair was created inside a genuine, certified secure enclave rather than a software emulator.
// Frontend WebAuthn Registration Snippet
async function registerPasskey(username) {
// Fetch registration options from Relying Party server
const optionsResponse = await fetch(`/api/register/options?username=\${username}`);
const creationOptions = await optionsResponse.json();
// Convert base64url strings to ArrayBuffers
creationOptions.challenge = Uint8Array.from(atob(creationOptions.challenge), c => c.charCodeAt(0));
creationOptions.user.id = Uint8Array.from(creationOptions.user.id, c => c.charCodeAt(0));
// Trigger browser WebAuthn dialog
const credential = await navigator.credentials.create({
publicKey: creationOptions
});
// Send the credential attestation back to server
await fetch("/api/register/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: credential.id,
rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),
type: credential.type,
response: {
attestationObject: btoa(String.fromCharCode(...new Uint8Array(credential.response.attestationObject))),
clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON)))
}
})
});
}
The Authentication Ceremony and Passkey Syncing
When a registered user attempts to log in, the authentication ceremony verifies their identity. The server generates a new random challenge and sends it to the client.
The client invokes navigator.credentials.get(), presenting the challenge. The authenticator prompts the user, locates the private key corresponding to the credential ID, signs the challenge, and returns an Assertion Object.
The server receives this assertion, retrieves the user's registered public key, and verifies the signature. If the signature matches, the user is authenticated, and a secure session is established. This process protects users from phishing because the authenticator binds the signature to the specific origin URL of the page, refusing to sign credentials if the client is on a spoofed domain.
A major development in this space is the introduction of Passkeys, which are discoverable credentials that sync across a user's devices via cloud accounts (such as iCloud Keychain or Google Password Manager). Unlike traditional WebAuthn credentials that were tied to a single physical device, Passkeys allow users to sign in from their phone, tablet, or desktop seamlessly.
To facilitate this, the registration options must specify residentKey: "required" or requireResidentKey: true. This instructs the authenticator to store the user metadata along with the private key on the device. When authenticating, the client does not need to specify which credential ID to use; the user simply selects their account from a browser-generated list, and the authenticator retrieves the correct key automatically.
- User Verification (UV): Assures that a specific user was verified by biometrics or a PIN (e.g., Apple TouchID).
- User Presence (UP): Assures that a physical user was present (e.g., tapping a physical button on a YubiKey).
- RP ID: The domain name of the web application, which WebAuthn uses to bind credentials to the specific origin.
Handling Recovery and Edge Cases
Designing a robust Passkey system requires addressing backup and recovery strategies. Since passkeys are bound to secure enclaves, if a user loses access to their cloud account or uses a device that does not support syncing, they could be locked out.
Implementing multi-factor fallbacks is essential. Architects should encourage users to register multiple passkeys (e.g., one on their phone and a secondary physical key) and provide secure, out-of-band recovery codes. Additionally, backend verification logic must be prepared to handle credential migration, verifying signature counters to detect cloned credentials, and decoding various COSE key formats (like ES256 and RS256) dynamically.
Passkeys Optimization at Bramsley
"Implementing passkeys requires cryptographic verification at the closest edge nodes to guarantee high speed and robust security."
Bramsley Digital Studio builds compliant, phishing-resistant WebAuthn gateways that run on global edge workers. By validating assertions at regional nodes, we bypass central database latency, ensuring seamless authentication. Partner with us to deploy secure, passwordless authentication. Talk to our security architects.