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.