Tutorial

How to Implement Passwordless Auth with WebAuthn

A comprehensive guide to implementing passwordless authentication using WebAuthn and FIDO2, from registration flows to production best practices.

December 17, 2025
11 min read
By TitaniumVault Team

Passwords are the weakest link in application security. They get reused, phished, brute-forced, and leaked in breaches. WebAuthn, the web standard behind passkeys and hardware security keys, offers a fundamentally better approach: cryptographic authentication that is phishing-resistant by design. In this guide, we walk through what WebAuthn is, how the protocol works under the hood, and how to bring passwordless login to your users with TitaniumVault.

What Is WebAuthn?

WebAuthn (Web Authentication) is a W3C standard and a core component of the FIDO2 project. It defines a browser API that allows web applications to use public-key cryptography instead of passwords to authenticate users. When a user registers with WebAuthn, their device generates a unique key pair. The private key never leaves the authenticator—whether that is a hardware security key like a YubiKey, a platform authenticator like Touch ID or Windows Hello, or a passkey synced through a cloud keychain. The server only ever receives and stores the public key.

Because the private key is bound to the authenticator and the authentication challenge is bound to the origin (the exact domain of your site), WebAuthn is resistant to phishing, credential stuffing, and replay attacks. Even if an attacker creates a pixel-perfect clone of your login page on a different domain, the authenticator will refuse to sign the challenge because the origin does not match.

Key Terminology

  • Relying Party (RP): Your application—the server that requests authentication and trusts the result.
  • Authenticator: The device or software that generates and stores the credential. This can be a roaming authenticator (USB security key, NFC key) or a platform authenticator (fingerprint sensor, face recognition built into the device).
  • Credential: The public-private key pair created during registration. Each credential is scoped to a specific relying party and user.
  • Passkey: A discoverable, often cloud-synced WebAuthn credential that can replace passwords entirely. Apple, Google, and Microsoft all support passkey synchronization across devices.
  • FIDO2: The umbrella project that includes WebAuthn (the browser API) and CTAP2 (the protocol for communicating with external authenticators over USB, NFC, or Bluetooth).

How WebAuthn Works

WebAuthn has two core ceremonies: registration (creating a new credential) and authentication (proving possession of an existing credential). Both follow a challenge-response pattern where the server issues a random challenge and the authenticator responds with a cryptographically signed assertion.

Registration Flow

Registration is the process of binding a new credential to a user account. Here is what happens step by step:

  1. Server generates options: Your backend creates a PublicKeyCredentialCreationOptions object. This includes a random challenge (at least 16 bytes of cryptographically random data), your relying party ID (typically your domain), the user's identifier, and the list of acceptable public-key algorithms (ES256 is the most widely supported).
  2. Browser calls the API: The frontend passes these options to navigator.credentials.create(). The browser displays a native UI prompting the user to interact with their authenticator—tap a security key, scan a fingerprint, or approve a passkey prompt.
  3. Authenticator creates a key pair: The authenticator generates a new public-private key pair, associates it with the relying party ID and user handle, and returns an attestation object containing the public key, credential ID, and a signature over the challenge and origin.
  4. Server verifies and stores: The backend verifies that the challenge matches, the origin is correct, and the attestation signature is valid. It then stores the public key and credential ID alongside the user's account. The private key never leaves the authenticator.

Authentication Flow

Authentication proves that the user possesses the private key corresponding to a previously registered credential:

  1. Server generates a challenge: The backend creates a PublicKeyCredentialRequestOptions object with a fresh random challenge and optionally a list of allowed credential IDs for the user.
  2. Browser calls the API: The frontend passes the options to navigator.credentials.get(). The browser prompts the user to interact with their authenticator.
  3. Authenticator signs the challenge: The authenticator locates the credential matching the relying party ID, verifies the user (biometric, PIN, or user presence), and signs the challenge along with authenticator data (which includes the origin hash and a signature counter).
  4. Server verifies the assertion: The backend uses the stored public key to verify the signature, confirms the challenge and origin match, checks that the signature counter has incremented (to detect cloned authenticators), and issues a session token upon success.

Browser and Platform Support

WebAuthn has reached broad support across all major browsers and operating systems. As of late 2025, support is effectively universal for modern environments:

  • Chrome / Edge: Full support since version 67, including passkeys, conditional UI (autofill integration), and cross-device authentication via QR codes for using a phone as an authenticator.
  • Safari: Full support since Safari 14 on macOS and iOS. Apple introduced passkey syncing through iCloud Keychain starting with iOS 16 and macOS Ventura, allowing users to authenticate across all their Apple devices seamlessly.
  • Firefox: Full support since version 60 for hardware security keys. Platform authenticator and passkey support was added in later versions and is now on par with other browsers.
  • Android: Google Play Services provides WebAuthn support across Android devices. Passkeys are synced through Google Password Manager, and Android devices can also act as cross-device authenticators for desktop browsers via FIDO2 BLE/hybrid transport.
  • Windows: Windows Hello acts as a platform authenticator, supporting fingerprint, facial recognition, and PIN-based verification. Passkeys are supported natively starting with Windows 11 23H2.

The practical implication is that you can offer WebAuthn to virtually all of your users today. For the rare cases where a user is on an older browser, you should provide a fallback authentication method (such as TOTP) while encouraging them to upgrade.

UX Considerations

A technically excellent WebAuthn implementation can still fail if the user experience is confusing. Authentication is a high-stakes moment—users need confidence that the process is secure and that they will not get locked out. Here are the key UX principles to follow:

Progressive Enrollment

Do not force WebAuthn on users during their first sign-up. Instead, let them create an account with a traditional method and then prompt them to register a passkey during a natural moment—after a successful login, in their security settings, or when they enable MFA. This reduces friction at the point of acquisition while still driving adoption over time.

Clear, Jargon-Free Messaging

Most users do not know what “WebAuthn” or “FIDO2” means. Use language they understand: “Sign in with your fingerprint,” “Use your security key,” or “Set up a passkey.” Explain what will happen before the browser prompt appears so users are not startled by a system dialog asking for biometric verification.

Multiple Credential Registration

Always allow users to register more than one credential. A user might have a security key on their keychain, Touch ID on their laptop, and Windows Hello on their desktop. If they lose access to one authenticator, they need another to fall back on. Display all registered credentials in the security settings with clear labels, registration dates, and last-used timestamps so users can manage them confidently.

Account Recovery

Plan for the scenario where a user loses all of their authenticators. Provide backup recovery codes at the time of WebAuthn enrollment and encourage users to store them in a secure location. You might also allow a verified email-based recovery flow that includes additional identity verification steps. The worst outcome is a user permanently locked out of their account.

Conditional UI and Autofill

Modern browsers support conditional mediation, which integrates passkeys directly into the browser's autofill dropdown. When a user focuses the username or email field, they see their passkey alongside any saved passwords. This is the smoothest possible login experience—no extra buttons to click, no separate “sign in with passkey” flow. Implementing conditional UI is as simple as passing mediation: 'conditional' to the navigator.credentials.get() call.

Implementing Passwordless Auth with TitaniumVault

TitaniumVault provides built-in WebAuthn support, handling the complex server-side cryptography, credential storage, and challenge management so you can focus on building a great user experience. Here is how to integrate it:

Step 1: Enable WebAuthn in Your Organization

In your TitaniumVault dashboard, navigate to your organization's authentication settings and enable WebAuthn as an MFA method. You can configure whether to allow platform authenticators (biometrics), roaming authenticators (security keys), or both. You can also set policies requiring WebAuthn for specific roles or security levels.

Step 2: Register a Credential

When a user wants to add a WebAuthn credential, your frontend requests registration options from the TitaniumVault API. The API returns a properly configured PublicKeyCredentialCreationOptions object with a fresh challenge, your relying party configuration, and the user's details. Your frontend passes this to the browser's WebAuthn API, and sends the resulting attestation back to TitaniumVault for verification and storage.

// 1. Request registration options from TitaniumVault
const options = await fetch('/api/webauthn/register/options', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ userId: currentUser.id })
}).then(r => r.json());

// 2. Call the browser WebAuthn API
const credential = await navigator.credentials.create({
  publicKey: options
});

// 3. Send the attestation to TitaniumVault for verification
await fetch('/api/webauthn/register/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(credential)
});

Step 3: Authenticate with a Credential

At login time, the flow mirrors registration. Your frontend requests authentication options from TitaniumVault, passes them to the browser's WebAuthn API, and sends the signed assertion back for verification. TitaniumVault validates the signature against the stored public key, checks the challenge and origin, verifies the signature counter, and returns a session token.

// 1. Request authentication options
const options = await fetch('/api/webauthn/authenticate/options', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: userEmail })
}).then(r => r.json());

// 2. Call the browser WebAuthn API
const assertion = await navigator.credentials.get({
  publicKey: options
});

// 3. Send the assertion to TitaniumVault for verification
const session = await fetch('/api/webauthn/authenticate/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(assertion)
}).then(r => r.json());

Step 4: Manage Credentials

TitaniumVault provides API endpoints to list, label, and revoke WebAuthn credentials. Build a security settings page where users can see all their registered authenticators, give them friendly names (e.g., “Work Laptop” or “YubiKey 5”), and remove ones they no longer use. The API also exposes last-used timestamps so you can highlight stale credentials that might indicate a lost device.

Best Practices

Following these best practices will help you build a WebAuthn implementation that is secure, reliable, and user-friendly:

1. Use Cryptographically Random Challenges

Every registration and authentication ceremony must use a fresh, server-generated challenge of at least 16 bytes from a cryptographically secure random number generator. Never reuse challenges. Store them server-side with a short TTL (typically 60 to 120 seconds) and invalidate them immediately after use. This prevents replay attacks.

2. Validate the Origin Rigorously

During verification, always check that the origin in the client data matches your expected origin exactly. Do not use substring matching or allow wildcards. The origin check is what makes WebAuthn phishing-resistant—weakening it defeats the purpose of the entire protocol.

3. Track and Verify Signature Counters

Each time an authenticator signs an assertion, it increments a signature counter. Your server should store this counter and verify that it increases with each authentication. If the counter does not increment or goes backward, it may indicate a cloned authenticator. While not all authenticators implement counters (some passkeys always return zero), you should still check when the counter is non-zero.

4. Prefer ES256 (ECDSA with P-256)

When specifying acceptable public-key algorithms in your registration options, list ES256 (COSE algorithm -7) first. It is the most widely supported algorithm across all authenticator types. You can also include RS256 as a fallback for older authenticators, but ES256 should be the default.

5. Require User Verification for Sensitive Actions

WebAuthn distinguishes between user presence (the user touched the authenticator) and user verification (the user proved their identity via biometric or PIN). For general login, user presence may be sufficient. For sensitive operations like changing security settings, transferring funds, or accessing privileged resources, require user verification by setting userVerification: 'required' in your options.

6. Plan for Credential Loss

Encourage every user to register at least two credentials on different devices. Generate one-time backup recovery codes during enrollment and stress the importance of storing them securely. Define a clear account recovery process that balances security with accessibility—requiring identity verification without making it so burdensome that users abandon the process.

7. Implement Attestation Thoughtfully

Attestation lets you verify the make and model of an authenticator during registration. For most applications, the “none” attestation conveyance is appropriate—you trust any authenticator and avoid the complexity of managing attestation certificate chains. If you are in a regulated industry or high-security environment where you need to restrict authentication to specific hardware models, use “direct” attestation and maintain an allowlist of trusted authenticator AAGUIDs.

Conclusion

WebAuthn represents the most significant improvement to web authentication since the password was invented. It eliminates the root causes of the most common attack vectors—phishing, credential stuffing, and password reuse—by replacing shared secrets with public-key cryptography bound to the user's device and your exact domain. With passkey support now built into every major operating system and browser, the barrier to adoption has never been lower.

TitaniumVault makes implementing WebAuthn straightforward. You get server-side challenge generation, attestation verification, credential storage, and signature counter tracking out of the box. Pair that with a thoughtful frontend that follows the UX principles outlined above, and you can offer your users a login experience that is both more secure and more convenient than passwords ever were.

Ready to go passwordless? Start your free 14-day TitaniumVault trial and enable WebAuthn for your organization in minutes, or explore our pricing plans to find the right fit for your team.

Want to learn more about secure authentication?

Explore our other articles on authentication best practices, or see how TitaniumVault can help secure your application.