Create your own
Lesson illustration

Biometric Authentication with Capacitor

Welcome back. In the previous lesson, we addressed the specifics of Android's runtime permission model, enabling your application to save files to shared storage directories. This was a critical step in mastering platform-specific integration.

This lesson shifts our focus from storage to security, tackling another key native feature you wanted to integrate: biometric authentication. We will install and implement a plugin that allows your app to use Face ID, Touch ID, or Android's fingerprint/face unlock to secure sensitive features. This not only enhances security but also provides a seamless login experience for your users, a significant improvement over traditional password entry on mobile devices. Our goal is to install the plugin, configure it for both iOS and Android, and implement a basic authentication flow to gate access to a feature within your React application.

1. Choosing the Right Biometrics Plugin

The world of open-source plugins evolves rapidly, and biometric authentication is a prime example. While the original goal was to use @capacitor-community/biometric-auth, that package has since been deprecated. This is a common scenario you've likely encountered in your front-end work. The community has moved towards newer, actively maintained alternatives.

For this lesson, we will use @capgo/capacitor-native-biometric. It's a popular, modern plugin that supports recent Capacitor versions and is well-documented in the resources we'll be using. This practical choice mirrors the real-world decision-making process of selecting and migrating dependencies in a project.

The article "Biometric Authentication in Capacitor Apps" provides a good overview of why this feature is valuable and what to consider when choosing a plugin.

Biometric Authentication in Capacitor Apps - Capgo

This article introduces the benefits of biometric authentication and the plugin landscape.

First, read the introduction to understand the core advantages. Then, review the first question in the FAQ section, which discusses the factors involved in selecting the best plugin for your app.

2. Installation and Native Configuration

The first step is to add the plugin to your monorepo and configure the native projects to declare their intent to use biometric hardware.

Step 2.1: Install the Plugin

Following your preference for bun, you'll add the package to the workspace. The plugin's documentation provides the exact commands.

Native Biometric Capacitor Plugin: Install, Setup & Examples - Capgo

This documentation page gives the precise installation commands.

Focus on the very first code block, which shows how to install and sync the plugin.

Execute these commands from the root of your Turborepo:







# Add the plugin to your project
bun add @capgo/capacitor-native-biometric







# Sync the new native code with your iOS and Android projects
bunx cap sync

The bunx cap sync command updates your native projects with the necessary plugin code, making the biometric APIs available to your app.

Step 2.2: Configure Native Platforms

Like the Filesystem plugin, the Biometrics plugin requires you to declare permissions in the native platform configuration files.

Biometric Authentication in Capacitor Apps - Capgo

This guide details the required configuration for both Android and iOS.

First, find the "Android Setup Steps" and locate the ">permission entries for AndroidManifest.xml. Then, under "iOS Setup Steps," find the Info.plist key for Face ID usage.

For Android:

Open android/app/src/main/AndroidManifest.xml and add the following permission inside the <manifest> tag, just before the <application> tag:

<uses-permission android:name="android.permission.USE_BIOMETRIC" />

The resource also mentions USE_FINGERPRINT, which is a deprecated permission but sometimes included for older devices. For modern apps targeting Android API 28+, USE_BIOMETRIC is sufficient.

For iOS:

Open ios/App/App/Info.plist and add the following key-value pair. This string will be shown to the user when the app first requests permission to use Face ID.

<key>NSFaceIDUsageDescription</key>
<string>This app uses Face ID to secure your data.</string>

You can customize the string to better match your app's context. After making these changes, it's a good practice to sync again to ensure everything is correctly configured.

bunx cap sync

3. Implementing the Authentication Flow

With the setup complete, you can now call the plugin's methods from your React code. The two primary functions we'll use are:

  • isAvailable(): To check if the device has biometric hardware and if it's configured by the user.
  • verifyIdentity(): To present the native biometric prompt (Face ID, fingerprint, etc.) to the user.

Let's create a new component to encapsulate this logic.

// src/components/BiometricGuard.tsx

import React, { useState, useEffect } from 'react';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
import { isPlatform } from '@ionic/react';

interface BiometricGuardProps {
  children: React.ReactNode;
}

const BiometricGuard: React.FC<BiometricGuardProps> = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isBiometricAvailable, setIsBiometricAvailable] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Check for biometric support on component mount
  useEffect(() => {
    // Biometrics are only available in the native app context
    if (!isPlatform('capacitor')) {
      // In a regular web browser, we can bypass this for development
      console.log('Not running on a native platform. Bypassing biometrics.');
      setIsAuthenticated(true);
      return;
    }

    const checkAvailability = async () => {
      try {
        const result = await NativeBiometric.isAvailable();
        setIsBiometricAvailable(result.isAvailable);
      } catch (e: any) {
        setError('Error checking biometric availability.');
        console.error(e);
      }
    };

    checkAvailability();
  }, []);

  const handleAuthentication = async () => {
    if (!isBiometricAvailable) {
      setError('Biometric authentication is not available on this device.');
      return;
    }

    try {
      // Present the biometric prompt
      const result = await NativeBiometric.verifyIdentity({
        reason: 'For easy and secure access',
        title: 'Verify your identity',
        subtitle: 'Log in using biometrics',
        description: 'Place your finger on the sensor or look at the camera.',
      });

      setIsAuthenticated(result.verified);
      if (!result.verified) {
        setError('Authentication failed.');
      }
    } catch (e: any) {
      setError('An error occurred during authentication.');
      console.error(e);
      setIsAuthenticated(false);
    }
  };
  
  // If authenticated, show the secured content
  if (isAuthenticated) {
    return <>{children}</>;
  }

  // Otherwise, show the authentication prompt button
  return (
    <div style={{ padding: '20px' }}>
      <h2>Authentication Required</h2>
      <p>Please verify your identity to access this content.</p>
      <button onClick={handleAuthentication} disabled={!isBiometricAvailable}>
        Authenticate with Biometrics
      </button>
      {!isBiometricAvailable && isPlatform('capacitor') && (
        <p style={{ color: 'red', marginTop: '10px' }}>
          Biometric hardware not available or not configured.
        </p>
      )}
      {error && <p style={{ color: 'red', marginTop: '10px' }}>{error}</p>}
    </div>
  );
};

export default BiometricGuard;

This component does the following:

  1. Checks Availability: On mount, it calls NativeBiometric.isAvailable() to determine if it should even offer the biometric option.
  2. Provides a Gateway: If the user is not authenticated, it renders a button to start the process. It disables the button if biometrics aren't available.
  3. Prompts the User: The handleAuthentication function calls NativeBiometric.verifyIdentity(), which triggers the OS-native UI for Face ID or fingerprint scanning.
  4. Conditionally Renders Content: If isAuthenticated is true, it renders its children. Otherwise, it renders the authentication UI.

You can now use this component to "wrap" and protect any part of your application:

// Example usage in one of your app pages

import BiometricGuard from '../components/BiometricGuard';

const SecurePage = () => {
  return (
    <div>
      <h1>My Application</h1>
      <BiometricGuard>
        {/* This content will only be shown after successful authentication */}
        <div>
          <h2>Secret Dashboard</h2>
          <p>Here is some sensitive information that is now protected.</p>
        </div>
      </BiometricGuard>
    </div>
  );
};

This pattern provides a clean way to secure application features, fulfilling the core requirement of this lesson.

Conclusion

You've successfully integrated biometric authentication, adding a layer of modern, user-friendly security to your application. This process involved navigating the plugin ecosystem, performing platform-specific configurations, and implementing a robust authentication flow in React.

Here are the key takeaways:

  • Open-source ecosystems evolve; it's crucial to identify and use modern, maintained packages like @capgo/capacitor-native-biometric instead of deprecated ones.
  • Plugin installation involves adding the dependency with bun and running bunx cap sync.
  • Native configuration is essential: you must declare USE_BIOMETRIC permission in AndroidManifest.xml and provide an NSFaceIDUsageDescription in Info.plist.
  • The core logic involves checking for hardware availability with isAvailable() before attempting to authenticate with verifyIdentity().
  • A wrapper component is an effective pattern for gating access to secure content within a React application.

In the next lesson, we will refine this implementation. You will learn how to build a reusable React hook that abstracts away the biometric availability checks and authentication logic, making it even easier to secure multiple components or routes throughout your app.

Can't find a good explanation? Sign up and we'll make it for you

Sign up