Expo Local Authentication
The `expo-local-authentication` package provides a unified JavaScript API for integrating device-specific biometric authentication methods such as Face ID and Touch ID on iOS, and the Fingerprint API on Android. This module, currently at version 55.0.13, is a core component within the Expo ecosystem, ensuring seamless cross-platform functionality for biometric identification. Its release cadence is tightly coupled with the Expo SDK releases, which typically align with new React Native versions, offering predictable updates and integration. Key differentiators include its deep integration with the Expo development workflow, abstracting away native module complexities, and providing a consistent API for checking hardware availability, user enrollment, and performing biometric authentication across both major mobile platforms. It simplifies the process of adding a crucial security layer to mobile applications developed with Expo and React Native, without requiring direct native code interaction.
Common errors
-
This app has not been granted the necessary permissions for Face ID.
cause Missing `NSFaceIDUsageDescription` in `app.json`'s `infoPlist` for iOS.fixAdd `"NSFaceIDUsageDescription": "Your app needs Face ID to authenticate you."` to `expo.ios.infoPlist` in your `app.json`. -
Biometric authentication is not available on this device.
cause The device either lacks biometric hardware or the `expo-local-authentication` module cannot access it.fixEnsure `LocalAuthentication.hasHardwareAsync()` returns `true` before proceeding. If it returns `false`, gracefully inform the user. -
No biometrics enrolled on the device.
cause The device has biometric hardware, but the user has not set up Face ID or Fingerprint authentication in their device settings.fixEnsure `LocalAuthentication.isEnrolledAsync()` returns `true` before attempting authentication. If it returns `false`, prompt the user to enroll biometrics in their device settings.
Warnings
- breaking On iOS, if using Face ID, you must add the `NSFaceIDUsageDescription` key to your `app.json` (under `expo.ios.infoPlist`). Without this, the app will crash when attempting to use Face ID on iOS 11 and above.
- gotcha For Android, ensure necessary permissions are declared in your `AndroidManifest.xml` (handled automatically by Expo in `app.json` for common cases). Specifically, `USE_BIOMETRIC` and `USE_FINGERPRINT`.
- deprecated The `LocalAuthentication.supportedAuthenticationTypesAsync` method has been deprecated. It is recommended to use `LocalAuthentication.getEnrolledLevelAsync` instead to determine the strength of biometric enrollment.
- gotcha It's crucial to always check `hasHardwareAsync()` and `isEnrolledAsync()` before calling `authenticateAsync()`. Failing to do so can lead to a poor user experience or unexpected errors if the device lacks hardware or the user hasn't set up biometrics.
Install
-
npm install expo-local-authentication -
yarn add expo-local-authentication -
pnpm add expo-local-authentication
Imports
- LocalAuthentication
const LocalAuthentication = require('expo-local-authentication');import * as LocalAuthentication from 'expo-local-authentication';
- authenticateAsync
await LocalAuthentication.authenticateAsync({ /* options */ }); - hasHardwareAsync
await LocalAuthentication.hasHardwareAsync();
Quickstart
import * as LocalAuthentication from 'expo-local-authentication';
import { Alert, Platform } from 'react-native'; // Assuming a React Native environment for Alert
async function performBiometricAuth() {
// 1. Check if biometric hardware is available
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (!hasHardware) {
Alert.alert('Authentication Error', 'Biometric hardware is not available on this device.');
return;
}
// 2. Check if biometrics are enrolled
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!isEnrolled) {
Alert.alert('Authentication Error', 'No biometrics (Face ID/Fingerprint) are enrolled. Please set them up in your device settings.');
return;
}
// 3. Attempt to authenticate
try {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate to access your account',
cancelLabel: 'Use Password', // Android specific (optional, customizes button text)
fallbackLabel: 'Enter Passcode', // iOS specific for older versions (optional)
disableDeviceFallback: false, // iOS specific (optional, disables passcode fallback)
});
if (result.success) {
Alert.alert('Success!', 'Biometric authentication successful!');
// Proceed with authenticated user flow
} else {
let errorMessage = 'Authentication failed.';
if (result.error === 'user_cancel') {
errorMessage = 'Authentication canceled by user.';
} else if (result.error === 'system_cancel') {
errorMessage = 'Authentication canceled by system (e.g., app in background).';
} else if (result.error === 'passcode_not_set') {
errorMessage = 'Device passcode not set, biometric authentication is not possible.';
} else if (result.error === 'not_enrolled') {
errorMessage = 'No biometrics enrolled on the device.';
} else if (result.error === 'not_available') {
errorMessage = 'Biometric hardware not available or supported.';
}
Alert.alert('Authentication Failed', `${errorMessage} (Error: ${result.error})`);
}
} catch (error) {
console.error('Biometric authentication unexpected error:', error);
Alert.alert('Error', 'An unexpected error occurred during authentication.');
}
}
// To integrate this in an Expo/React Native component:
// import React from 'react';
// import { Button, View } from 'react-native';
// const BiometricAuthScreen = () => (
// <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
// <Button title="Authenticate with Biometrics" onPress={performBiometricAuth} />
// </View>
// );
// export default BiometricAuthScreen;
// For immediate testing (e.g., in a standalone script or effect hook):
// (async () => {
// if (Platform.OS === 'web') {
// console.warn('Local authentication is not available on web.');
// return;
// }
// // await performBiometricAuth();
// })();