Authentication & Identity

How to Add FaceID & TouchID to Your Web App (WebAuthn)

March 22, 2026
8 Min Read

If you are building a secure web application, relying solely on passwords is no longer enough. Passwords get leaked, forgotten, or brute-forced. Enter WebAuthn—a native browser API that allows you to authenticate users using their device's built-in biometrics, like Apple's FaceID, TouchID, or Windows Hello.

But WebAuthn isn't just for logging in. In this tutorial, we will explore a unique use case: using client-side biometrics to block social media link-preview bots from prematurely triggering "burn-after-reading" payload links.

The "Link Preview Bot" Problem

Imagine you build a highly secure, one-time-use secure link. You send it to a coworker on Slack, iMessage, or WhatsApp.

Before your coworker even clicks the link, Slack's server sends an automated bot (a web crawler) to fetch the URL so it can generate a pretty preview card with a title and image. Because the bot "visited" the link, your backend registers it as opened, and the payload is permanently destroyed. When the actual human clicks it, they get a 404 error.

You could use a CAPTCHA, but they are terrible for user experience. Instead, we can use navigator.credentials.create() to ensure a real human holding a physical device is opening the link.

Step 1: Check for WebAuthn Support

Before invoking the biometric prompt, you must verify that the user's browser and operating system support Public Key Cryptography.

function isBiometricSupported() {
    return window.PublicKeyCredential !== undefined && 
           typeof window.PublicKeyCredential === 'function';
}

if (!isBiometricSupported()) {
    console.warn("This device does not support WebAuthn.");
    // Fallback to a standard PIN or password prompt
}

Step 2: Generating the Cryptographic Challenge

To prevent replay attacks, the WebAuthn API requires a random "challenge" buffer. When registering a full passkey to a backend server, this challenge must come from your database. However, for our local client-side bot gate, we can generate it directly in the browser.

const challenge = new Uint8Array(32);
window.crypto.getRandomValues(challenge);

Step 3: Invoking the Biometric Prompt

Now we use the navigator.credentials API to ask the operating system to verify the user. We specify authenticatorSelection: { userVerification: "required" } to force the OS to use FaceID, TouchID, or a device PIN.

async function verifyHumanWithBiometrics() {
    try {
        const challenge = new Uint8Array(32);
        window.crypto.getRandomValues(challenge);
        
        // This triggers the native OS FaceID/TouchID popup
        const credential = await navigator.credentials.create({
            publicKey: {
                challenge: challenge,
                rp: { 
                    name: "Secure Vault App", 
                    id: window.location.hostname 
                },
                user: { 
                    id: new Uint8Array(16), 
                    name: "user@local", 
                    displayName: "Local Verification" 
                },
                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
                authenticatorSelection: { 
                    authenticatorAttachment: "platform",
                    userVerification: "required" 
                },
                timeout: 60000 // 1 minute timeout
            }
        });
        
        // If the promise resolves, the human successfully authenticated
        return true;
        
    } catch (err) {
        // The user cancelled the prompt, or it failed
        console.error("Biometric verification failed:", err);
        return false;
    }
}

When a Slack or WhatsApp bot hits your page, it cannot execute this API because bots do not have physical biometric hardware or device PINs. The code halts, the backend is never queried, and your burn-after-reading payload remains perfectly safe until the human user passes the check.

See WebAuthn in Action

We implemented this exact biometric gate in ZeroKey, our open-source secure sharing vault. It ensures that only the intended physical recipient can trigger the decryption and data-destruction sequence.

Try generating a secure link and opening it on your mobile device to test the native FaceID/TouchID integration.

Conclusion

The WebAuthn API is incredibly powerful. While it is primarily designed to replace passwords on backend servers, utilizing it as a local, client-side human-verification tool provides an elegant, highly secure alternative to annoying CAPTCHAs. It keeps your database safe from web crawlers while providing a frictionless, premium experience for your actual users.