/web-app-features

How to Add Biometric Login to Your Web App

Learn how to add secure biometric login to your web app with our easy step-by-step guide. Enhance user security today!

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add Biometric Login to Your Web App

How to Add Biometric Login to Your Web App

 

The Rise of Biometric Authentication

 

Biometric authentication has evolved from sci-fi fantasy to everyday reality. Your users already unlock their phones with their fingerprints and faces—why not bring that same convenience to your web application? Beyond the cool factor, biometrics offers a compelling blend of security and usability that traditional passwords simply can't match.

 

The Web Authentication API (WebAuthn): Your New Best Friend

 

The good news is that implementing biometric authentication no longer requires deep platform-specific knowledge. The Web Authentication API (WebAuthn) provides a standardized way to implement biometric login across browsers and devices. It's part of the FIDO2 specifications and is supported by all major browsers including Chrome, Firefox, Safari, and Edge.

 

How Biometric Authentication Actually Works

 

Before diving into code, let's understand what happens behind the scenes:

 

  1. Your web app requests authentication through the browser
  2. The browser activates the device's biometric sensor (fingerprint reader, facial recognition camera, etc.)
  3. The user authenticates with their biometric data
  4. The sensor verifies the biometric locally (your server never sees the actual biometric data)
  5. If successful, a cryptographic proof of authentication is sent to your server

 

This approach keeps sensitive biometric data on the user's device while giving your server cryptographic assurance that authentication succeeded.

 

Implementation: A Step-by-Step Guide

 

Step 1: Set Up Your Backend

 

You'll need server-side components to handle registration and verification. Here's a Node.js example using Express and the @simplewebauthn/server library:

 

// Install dependencies:
// npm install express @simplewebauthn/server base64url

const express = require('express');
const { generateRegistrationOptions, verifyRegistrationResponse, 
        generateAuthenticationOptions, verifyAuthenticationResponse } = require('@simplewebauthn/server');
const base64url = require('base64url');

const app = express();
app.use(express.json());

// In-memory storage (use a database in production)
const userDB = {};

// Random ID generator helper
const generateRandomID = () => base64url(Buffer.from(Array(32).fill(0).map(() => Math.floor(Math.random() * 256))));

// Your domain - MUST match your site's origin
const rpID = 'example.com';
const rpName = 'Your App Name';
const origin = `https://${rpID}`;

 

Step 2: Registration Endpoint - Let Users Register Their Biometrics

 

app.post('/api/register/options', (req, res) => {
  const { username } = req.body;
  
  // Create a new user or get existing one
  const user = userDB[username] || {
    id: generateRandomID(),
    username,
    devices: [],
    currentChallenge: null
  };
  
  // Save user if new
  if (!userDB[username]) userDB[username] = user;
  
  // Generate registration options
  const options = generateRegistrationOptions({
    rpName,
    rpID,
    userID: user.id,
    userName: username,
    // Prevent users from re-registering the same device
    excludeCredentials: user.devices.map(device => ({
      id: device.credentialID,
      type: 'public-key',
      transports: device.transports || ['internal']
    })),
    authenticatorSelection: {
      // Require biometrics when available
      userVerification: 'preferred',
      // Prefer platform authenticators (like TouchID, FaceID, Windows Hello)
      authenticatorAttachment: 'platform'
    }
  });
  
  // Save the challenge for verification later
  user.currentChallenge = options.challenge;
  
  res.json(options);
});

app.post('/api/register/verify', async (req, res) => {
  const { username, attestationResponse } = req.body;
  const user = userDB[username];
  
  if (!user) {
    return res.status(400).json({ error: 'User not found' });
  }
  
  try {
    // Verify the attestation response
    const verification = await verifyRegistrationResponse({
      credential: attestationResponse,
      expectedChallenge: user.currentChallenge,
      expectedOrigin: origin,
      expectedRPID: rpID
    });
    
    if (verification.verified) {
      // Add the new device to user's devices
      const { credentialID, credentialPublicKey } = verification.registrationInfo;
      
      user.devices.push({
        credentialID,
        credentialPublicKey,
        transports: attestationResponse.transports || ['internal'],
        registered: new Date().toISOString()
      });
      
      return res.json({ success: true });
    }
    
    res.status(400).json({ error: 'Verification failed' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: error.message });
  }
});

 

Step 3: Authentication Endpoints - Let Users Log In With Biometrics

 

app.post('/api/login/options', (req, res) => {
  const { username } = req.body;
  const user = userDB[username];
  
  if (!user || user.devices.length === 0) {
    return res.status(400).json({ error: 'User not registered with biometrics' });
  }
  
  // Generate authentication options
  const options = generateAuthenticationOptions({
    rpID,
    // Only allow credentials registered to this user
    allowCredentials: user.devices.map(device => ({
      id: device.credentialID,
      type: 'public-key',
      transports: device.transports || ['internal']
    })),
    userVerification: 'preferred'
  });
  
  // Save challenge for verification
  user.currentChallenge = options.challenge;
  
  res.json(options);
});

app.post('/api/login/verify', async (req, res) => {
  const { username, assertionResponse } = req.body;
  const user = userDB[username];
  
  if (!user) {
    return res.status(400).json({ error: 'User not found' });
  }
  
  // Find the authenticator that was used
  const matchingDevice = user.devices.find(device => 
    device.credentialID === assertionResponse.id);
  
  if (!matchingDevice) {
    return res.status(400).json({ error: 'Authenticator not registered for this user' });
  }
  
  try {
    // Verify the authentication response
    const verification = await verifyAuthenticationResponse({
      credential: assertionResponse,
      expectedChallenge: user.currentChallenge,
      expectedOrigin: origin,
      expectedRPID: rpID,
      authenticator: {
        credentialPublicKey: matchingDevice.credentialPublicKey,
        credentialID: matchingDevice.credentialID,
        counter: matchingDevice.counter || 0
      }
    });
    
    if (verification.verified) {
      // Update the authenticator's counter
      matchingDevice.counter = verification.authenticationInfo.newCounter;
      
      // Here you'd typically create a session or JWT token
      return res.json({ 
        success: true, 
        token: 'your-auth-token-here' // Generate real token in production
      });
    }
    
    res.status(400).json({ error: 'Verification failed' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

 

Step 4: Frontend Implementation

 

Now let's implement the client-side code using the @simplewebauthn/browser library:

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Biometric Login Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script>
</head>
<body>
  <h1>Biometric Authentication Demo</h1>
  
  <div id="register-section">
    <h2>Register</h2>
    <input type="text" id="register-username" placeholder="Username">
    <button id="register-button">Register with Biometrics</button>
    <div id="register-status"></div>
  </div>
  
  <div id="login-section">
    <h2>Login</h2>
    <input type="text" id="login-username" placeholder="Username">
    <button id="login-button">Login with Biometrics</button>
    <div id="login-status"></div>
  </div>
  
  <script>
    // Registration process
    document.getElementById('register-button').addEventListener('click', async () => {
      const username = document.getElementById('register-username').value;
      const statusEl = document.getElementById('register-status');
      
      if (!username) {
        statusEl.textContent = 'Please enter a username';
        return;
      }
      
      try {
        statusEl.textContent = 'Starting registration...';
        
        // 1. Get registration options from server
        const optionsRes = await fetch('/api/register/options', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ username })
        });
        
        const options = await optionsRes.json();
        
        if (!optionsRes.ok) {
          throw new Error(options.error || 'Failed to get registration options');
        }
        
        // 2. Pass options to browser's WebAuthn API
        statusEl.textContent = 'Please follow the instructions to authenticate...';
        const attestation = await SimpleWebAuthnBrowser.startRegistration(options);
        
        // 3. Send response to server for verification
        const verificationRes = await fetch('/api/register/verify', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ username, attestationResponse: attestation })
        });
        
        const verificationResult = await verificationRes.json();
        
        if (!verificationRes.ok || !verificationResult.success) {
          throw new Error(verificationResult.error || 'Registration failed');
        }
        
        statusEl.textContent = 'Registration successful! You can now log in with biometrics.';
      } catch (error) {
        console.error(error);
        statusEl.textContent = `Error: ${error.message}`;
      }
    });
    
    // Login process
    document.getElementById('login-button').addEventListener('click', async () => {
      const username = document.getElementById('login-username').value;
      const statusEl = document.getElementById('login-status');
      
      if (!username) {
        statusEl.textContent = 'Please enter a username';
        return;
      }
      
      try {
        statusEl.textContent = 'Starting login...';
        
        // 1. Get authentication options from server
        const optionsRes = await fetch('/api/login/options', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ username })
        });
        
        const options = await optionsRes.json();
        
        if (!optionsRes.ok) {
          throw new Error(options.error || 'Failed to get login options');
        }
        
        // 2. Pass options to browser's WebAuthn API
        statusEl.textContent = 'Please follow the instructions to authenticate...';
        const assertion = await SimpleWebAuthnBrowser.startAuthentication(options);
        
        // 3. Send response to server for verification
        const verificationRes = await fetch('/api/login/verify', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ username, assertionResponse: assertion })
        });
        
        const verificationResult = await verificationRes.json();
        
        if (!verificationRes.ok || !verificationResult.success) {
          throw new Error(verificationResult.error || 'Authentication failed');
        }
        
        // Store token for authenticated session
        localStorage.setItem('authToken', verificationResult.token);
        
        statusEl.textContent = 'Login successful!';
      } catch (error) {
        console.error(error);
        statusEl.textContent = `Error: ${error.message}`;
      }
    });
  </script>
</body>
</html>

 

Key Implementation Considerations

 

Graceful Degradation

 

Not all devices support biometrics. Implement feature detection and provide password fallback:

 

// Check if WebAuthn is supported
if (!window.PublicKeyCredential) {
  // WebAuthn not supported - hide biometric options and show password login
  document.getElementById('biometric-section').style.display = 'none';
  document.getElementById('password-section').style.display = 'block';
} else {
  // Check if the device has biometric capability
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
    .then(available => {
      if (!available) {
        // Platform cannot verify users biometrically
        document.getElementById('biometric-warning').textContent = 
          'Your device doesn\'t support biometric authentication. You can still register a security key.';
      }
    });
}

 

Security & Storage Considerations

 

  • Store credential data securely in your database with proper encryption
  • Never persist raw biometric data - WebAuthn ensures this doesn't happen
  • Implement rate limiting on registration and authentication endpoints
  • Use HTTPS exclusively (WebAuthn requires secure contexts)

 

Account Recovery

 

Always provide recovery options for users who:

 

  • Get a new device
  • Have temporary biometric issues (injury, etc.)
  • Experience technical problems with their authenticator

 

This typically means providing email-based recovery links or backup codes at registration time.

 

Real-World Considerations

 

Performance Impact

 

Biometric authentication generally completes faster than typing a password, though the initial prompt might feel like an extra step. To mitigate this:

 

  • Prefetch authentication options while the login page loads
  • Implement "remember this device" functionality for repeat users
  • Optimize your backend response times for WebAuthn endpoints

 

The Business Case for Biometrics

 

Besides the security benefits, here's how biometrics can positively impact your metrics:

 

  • Reduced abandonment: Users forget passwords, not faces or fingerprints
  • Fewer support tickets: Password resets are a common support burden
  • Higher conversion: Faster, smoother authentication means less friction
  • Enhanced security posture: Protection against credential stuffing and phishing

 

Integration Strategies

 

Start Small

 

Consider these implementation strategies:

 

  • Opt-in beta: Let enthusiastic users try it first while gathering feedback
  • Progressive enhancement: Offer it alongside passwords, not as a replacement
  • High-value segments first: Roll out to power users or premium tiers initially

 

Platform-Specific Optimizations

 

Different platforms have different biometric capabilities:

 

  • iOS/Safari: Leverages Touch ID and Face ID
  • Android/Chrome: Uses fingerprint sensors and facial recognition
  • Windows/Edge: Integrates with Windows Hello (facial, fingerprint, or PIN)
  • macOS/Safari: Uses Touch ID on newer MacBooks

 

The WebAuthn API abstracts these differences, but testing across platforms remains important.

 

Measuring Success

 

Once implemented, track these metrics:

 

  • Adoption rate: percentage of users enabling biometric login
  • Authentication success rate compared to passwords
  • Login time (usually faster than password entry)
  • Reduction in password reset requests
  • User satisfaction (via surveys or feedback)

 

Conclusion: The Post-Password Future

 

Implementing biometric authentication isn't just a technical upgrade—it's a user experience transformation. As passwords increasingly become a liability rather than a protection, biometrics offer a path toward more secure, convenient authentication.

 

The WebAuthn standard has finally made cross-platform biometric authentication practical for web applications. With the implementation approach outlined here, you can bring this technology to your users today, positioning your application at the forefront of modern authentication practices.

 

Remember: the goal isn't just to replace passwords with something more secure, but to make security so seamless that it enhances rather than hinders the user experience.

Ship Biometric Login 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 Biometric Login Usecases

Explore the top 3 practical biometric login use cases to enhance your web app’s security and user experience.

 

Financial Transaction Authentication

 

A security layer that uses unique physical traits to authorize sensitive banking operations and payments, dramatically reducing fraud while eliminating the friction of traditional authentication methods.

 

  • Conversion impact: Reduces payment abandonment by up to 70% compared to SMS verification codes while maintaining stronger security.
  • Implementation value: Directly addresses regulatory requirements like PSD2/SCA in Europe while offering a competitive edge in user experience.
  • Risk mitigation: Creates a physical barrier against credential stuffing and social engineering attacks that purely digital authentication systems can't match.

 

Healthcare Record Access

 

A privacy-preserving authorization system allowing medical professionals and patients to access sensitive health information through inherent biological identifiers rather than forgettable credentials.

 

  • Compliance advantage: Aligns perfectly with HIPAA and other healthcare privacy regulations through its strong audit trail and access controls.
  • Operational efficiency: Eliminates password reset requests which typically account for 20-30% of healthcare IT support tickets.
  • Contextual security: Can be configured to require escalating biometric factors based on the sensitivity of the requested medical data.

 

Enterprise Workforce Authentication

 

A frictionless access system that enables employees to authenticate across physical spaces and digital systems using the same biometric identifier, eliminating credential juggling.

 

  • Cost reduction: Typically delivers 40-60% reduction in identity management overhead compared to traditional password/badge systems.
  • Security posture: Creates a non-repudiable audit trail for compliance-sensitive operations that traditional credentials can't provide.
  • Experience benefit: Reduces authentication time by 3-4 seconds per transaction, which compounds dramatically across thousands of daily authentications.

 


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â