Skip to content

Web Authentication API

This lesson explains Web Authentication API with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.

HTML5 Web Authentication API Overview

The HTML5 Web Authentication API, also known as WebAuthn, allows websites to register and authenticate users with public-key cryptography instead of passwords alone. It supports platform authenticators such as Face ID, Touch ID, and Windows Hello, as well as roaming security keys.

WebAuthn is designed to reduce phishing risk and improve login security. The browser talks to an authenticator, but the server still validates challenges, stores public keys, and verifies signatures during registration and sign-in.

Feature Description
API Name Web Authentication API (WebAuthn)
Main Methods navigator.credentials.create() and navigator.credentials.get()
Credential Type PublicKeyCredential
Authentication Model Public-key cryptography
Common Authenticators Biometrics, device PIN, security keys, passkeys
Common Uses Passwordless login, MFA, passkey sign-in

Basic WebAuthn Registration Flow

<button id="registerPasskey" type="button">
  Create Passkey
</button>
const registerButton =
  document.getElementById("registerPasskey");

registerButton.addEventListener("click", async () => {
  if (!window.PublicKeyCredential) {
    alert("WebAuthn is not supported.");
    return;
  }

  const optionsResponse =
    await fetch("/api/webauthn/register/options", {
      method: "POST"
    });

  const options =
    await optionsResponse.json();

  const publicKeyOptions = {
    ...options,
    challenge: base64UrlToArrayBuffer(options.challenge),
    user: {
      ...options.user,
      id: base64UrlToArrayBuffer(options.user.id)
    }
  };

  const credential =
    await navigator.credentials.create({
      publicKey: publicKeyOptions
    });

  await fetch("/api/webauthn/register/verify", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(credential)
  });
});

Registration starts on the server, which creates a challenge and registration options. The browser creates a credential with the authenticator, then sends the result back to the server for verification.

How Web Authentication API Works

WebAuthn always involves both the browser and the server. The browser handles user interaction with the authenticator, while the server generates challenges and verifies cryptographic responses.

Step Description Example Syntax
Server Challenge Server creates a one-time challenge and options. POST /register/options
Create Credential Browser registers a new public key. navigator.credentials.create()
Verify Registration Server stores the public key credential. POST /register/verify
Authentication Challenge Server sends allowed credential IDs. POST /login/options
Get Credential User signs in with authenticator. navigator.credentials.get()
Verify Assertion Server validates the signature. POST /login/verify

WebAuthn Core Components

Component Description Purpose
Relying Party The website or application using WebAuthn. Defines domain and registration policy.
Authenticator Device or security key that creates keys. Performs biometric or PIN verification.
Challenge Random server-generated value. Prevents replay attacks.
Public Key Credential Credential created during registration. Used for future sign-in.
Attestation Proof returned during registration. Helps server trust the authenticator.
Assertion Signature returned during login. Proves possession of the private key.

Registration Options Example

const registrationOptions = {
  challenge: crypto.getRandomValues(
    new Uint8Array(32)
  ),
  rp: {
    name: "Example App",
    id: "example.com"
  },
  user: {
    id: new TextEncoder().encode("user-123"),
    name: "alex@example.com",
    displayName: "Alex"
  },
  pubKeyCredParams: [
    { type: "public-key", alg: -7 },
    { type: "public-key", alg: -257 }
  ],
  authenticatorSelection: {
    residentKey: "preferred",
    userVerification: "preferred"
  },
  timeout: 60000,
  attestation: "none"
};

Registration options tell the browser which algorithms to support, which relying party is requesting the credential, and how strictly user verification should be enforced.

WebAuthn Login Example

<button id="loginPasskey" type="button">
  Sign in with Passkey
</button>
const loginButton =
  document.getElementById("loginPasskey");

loginButton.addEventListener("click", async () => {
  const optionsResponse =
    await fetch("/api/webauthn/login/options", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        email: "alex@example.com"
      })
    });

  const options =
    await optionsResponse.json();

  const assertion =
    await navigator.credentials.get({
      publicKey: {
        ...options,
        challenge: base64UrlToArrayBuffer(options.challenge),
        allowCredentials: options.allowCredentials.map(
          (item) => ({
            ...item,
            id: base64UrlToArrayBuffer(item.id)
          })
        )
      }
    });

  const verifyResponse =
    await fetch("/api/webauthn/login/verify", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(assertion)
    });

  const result =
    await verifyResponse.json();

  console.log("Login success:", result.success);
});

During login, the server sends a challenge and a list of allowed credential IDs. The authenticator signs the challenge, and the server verifies the signature before creating a session.

Convert Base64URL Values for WebAuthn

function base64UrlToArrayBuffer(value) {
  const base64 =
    value
      .replace(/-/g, "+")
      .replace(/_/g, "/");

  const padded =
    base64 + "=".repeat((4 - base64.length % 4) % 4);

  const binary =
    atob(padded);

  const bytes =
    new Uint8Array(binary.length);

  for (let i = 0; i < binary.length; i += 1) {
    bytes[i] = binary.charCodeAt(i);
  }

  return bytes.buffer;
}

function arrayBufferToBase64Url(buffer) {
  const bytes =
    new Uint8Array(buffer);

  let binary = "";

  bytes.forEach((byte) => {
    binary += String.fromCharCode(byte);
  });

  return btoa(binary)
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/g, "");
}

WebAuthn uses binary values such as challenges and credential IDs. Servers usually send them as base64url strings, so the client must convert them to ArrayBuffer before calling the API.

Handle WebAuthn Errors

async function registerPasskey(options) {
  try {
    const credential =
      await navigator.credentials.create({
        publicKey: options
      });

    return { status: "created", credential };
  } catch (error) {
    if (error.name === "NotAllowedError") {
      return { status: "cancelled" };
    }

    if (error.name === "InvalidStateError") {
      return { status: "already-registered" };
    }

    if (error.name === "SecurityError") {
      return { status: "insecure-context" };
    }

    console.error("WebAuthn error:", error);
    return { status: "failed" };
  }
}

Users may cancel biometric prompts, credentials may already exist, and WebAuthn requires a secure context. Handle these cases clearly instead of showing generic login failures.

Feature Detection

function supportsWebAuthn() {
  return (
    window.PublicKeyCredential &&
    typeof window.PublicKeyCredential
      .isUserVerifyingPlatformAuthenticatorAvailable ===
      "function"
  );
}

async function checkPlatformAuthenticator() {
  if (!supportsWebAuthn()) {
    return false;
  }

  return PublicKeyCredential
    .isUserVerifyingPlatformAuthenticatorAvailable();
}

if (await checkPlatformAuthenticator()) {
  console.log("Platform authenticator available.");
} else {
  console.log("Platform authenticator not available.");
}

Check both API support and platform authenticator availability before showing passkey buttons. Some browsers support WebAuthn but only with external security keys.

Common Web Authentication API Methods

Method Description Common Use
navigator.credentials.create() Creates a new public key credential. Register a passkey or security key.
navigator.credentials.get() Requests an authentication assertion. Sign in with an existing credential.
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() Checks for built-in authenticators. Show passkey UI only when supported.
PublicKeyCredential.isConditionalMediationAvailable() Checks for autofill-style passkey login. Enable conditional UI on supported browsers.

Common Web Authentication API Use Cases

  • Passwordless sign-in with device biometrics or PIN.
  • Passkey-based login across synced devices.
  • Strong multi-factor authentication with security keys.
  • High-security admin or banking account access.
  • Reducing reliance on reusable passwords.
  • Phishing-resistant authentication for enterprise apps.

Web Authentication API Best Practices

  • Always generate challenges on the server, never in client-only code.
  • Verify attestation and assertion responses on the server.
  • Use HTTPS and a correct relying party ID such as your domain.
  • Offer password or recovery options during passkey rollout.
  • Handle cancellation and unsupported-device cases gracefully.
  • Store credential IDs and public keys securely in your database.
  • Test on mobile, desktop, and security-key scenarios.

Security Considerations

  • WebAuthn reduces phishing risk because credentials are bound to your site origin.
  • Private keys remain on the authenticator and are not exposed to JavaScript.
  • Server-side verification is mandatory for secure authentication.
  • Challenges must be unique, random, and short-lived.
  • Do not treat client-side credential creation as proof of login by itself.
  • Plan account recovery before removing passwords entirely.

Common Web Authentication API Mistakes

  • Trying to implement WebAuthn without server-side verification.
  • Using the wrong relying party ID for the current domain.
  • Forgetting to convert base64url values to ArrayBuffer.
  • Assuming every device has a platform authenticator.
  • Not handling user cancellation during biometric prompts.
  • Reusing challenges or skipping challenge expiration checks.
  • Removing all fallback login methods too early.

WebAuthn vs Password-Only Login

Feature WebAuthn Password-Only Login
Phishing Resistance Strong origin binding. Users can be tricked on fake sites.
Secret Storage Private key stays on authenticator. Password may be reused or leaked.
User Experience Fast biometric or passkey sign-in. Requires typing and remembering passwords.
Implementation Requires browser and server coordination. Simpler but less secure by default.

Key Takeaways

  • The Web Authentication API enables public-key registration and sign-in in the browser.
  • Use navigator.credentials.create() for registration and navigator.credentials.get() for login.
  • WebAuthn always requires secure server-side challenge generation and verification.
  • Passkeys and security keys provide stronger, phishing-resistant authentication.
  • Feature detection, error handling, and recovery options are essential in production.

Pro Tip

Start with one passkey registration and login flow on a test domain. Verify server challenge handling first, then add biometric prompts, fallback login, and account recovery once the core cryptographic flow works reliably.