Skip to content

Instantly share code, notes, and snippets.

@eznix86
Forked from alan-delgado/BiometricLogin.md
Created March 3, 2025 08:15
Show Gist options
  • Save eznix86/7ee386dd94bda8cb43e9ebb752f6338a to your computer and use it in GitHub Desktop.
Save eznix86/7ee386dd94bda8cb43e9ebb752f6338a to your computer and use it in GitHub Desktop.
Biometric Login Implementation

Biometric Login implementation

Biometric login allows you to use FaceID and TouchID (iOS) or the Biometric Prompt (Android) to authenticate the user with a face or fingerprint scan.

This guide is intended for React Native projects using Expo.

Instructions

  1. Install expo LocalAuthentication to be able to authenticate users using biometrics

  2. Install expo SecureStore to securely store the user's credentials and be able to login using its email and password

    expo install expo-local-authentication expo-secure-store
  3. Add NSFaceIDUsageDescription to your Info.plist (Required in bare React Native projects):

    <key>NSFaceIDUsageDescription</key>
    <string>Necesitamos usar FaceID para iniciar sesión</string>
  4. In your Login with email and password component check if the device supports biometrics and if it's enabled

    useEffect(() => {
      (async () => {
        try {
          const compatible = await LocalAuthentication.hasHardwareAsync();
          setIsCompatible(compatible);
          const value = await AsyncStorage.getItem("biometrics");
          const biometrics = value ? JSON.parse(value) : false;
          setIsBiometricsEnabled(biometrics);
        } catch (error) {
          console.log(error);
        }
      })();
    }, []);
  5. Then add the function to handle the biometric login

    const handleBiometricAuth = async () => {
      try {
        // Check if biometrics are saved on the user’s device
        const savedBiometrics = await LocalAuthentication.isEnrolledAsync();
        if (!savedBiometrics) {
          throw new Error("No se encontraron huellas digitales registradas");
        }
    
        const email = await SecureStore.getItemAsync("email");
        if (!email) {
          throw new Error(
            "Inicia sesión con tu correo para poder usar la huella"
          );
        }
    
        const biometricAuth = await LocalAuthentication.authenticateAsync({
          promptMessage: "Huella digital",
          cancelLabel: "Usar contraseña",
        });
        if (!biometricAuth.success) return;
    
        const password = await SecureStore.getItemAsync("password");
        if (!password) {
          throw new Error(
            "Inicia sesión con tu correo para poder usar la huella"
          );
        }
    
        onSubmitSuccess(email, password); // signInWithEmailAndPassword
      } catch (e) {
        Toast.show(String(e).substring(7), {
          position: Toast.positions.CENTER,
        });
      }
    };
  6. In your onSubmitSuccess function, save the user's credentials after it successfuly sings in

    try {
      await signInWithEmailAndPassword(email, password);
      // Save user's credential in the Secure Store
      await SecureStore.setItemAsync("email", email);
      await SecureStore.setItemAsync("password", password);
    ...

Those are the main steps to successfuly implement biometric login, it is recommended to add an option to enable or disable biometric authentication in your app's settings so the user can choose if he wants to use biometric login or not.

Automatically sign out the user

Also, when implementing biometric login, it's a common behaviour that your app will sign out the user after a certain amount of time. To implement that functionality, you can use React Native AppState and the following custom hook.

// useIdleSignOut customHook

import AsyncStorage from "@react-native-async-storage/async-storage";
import { useNavigation } from "@react-navigation/native";
import dayjs from "dayjs";
import { useCallback, useEffect, useRef } from "react";
import { AppState, AppStateStatus } from "react-native";

import useSignOut from "./useSignOut";
import useAuth from "contexts/auth/auth.context.hooks";
import { RootNavigatorPropList } from "navigation/Navigator.types";

const useIdleSignOut = () => {
  const { navigate } = useNavigation<RootNavigatorPropList>();
  const { isAnonymous } = useAuth();
  const signOut = useSignOut();
  const appState = useRef<AppStateStatus | undefined>();

  const idleHandler = useCallback(async () => {
    const lastTimeActive = dayjs(await AsyncStorage.getItem("idleTime"));
    const timeDifference = dayjs().diff(lastTimeActive, "minutes");
    if (timeDifference >= 10) {
      signOut();
      navigate("LoginScreen");
    }
    AsyncStorage.removeItem("idleTime");
  }, [navigate, signOut]);

  const appStateChangeHandler = useCallback(
    (nextAppState: AppStateStatus) => {
      if (isAnonymous) return;

      if (nextAppState === "background") {
        AsyncStorage.setItem("idleTime", dayjs().toString());
      }

      if (
        !appState.current ||
        (appState.current.match(/inactive|background/) &&
          nextAppState === "active")
      ) {
        idleHandler();
      }

      appState.current = nextAppState;
    },
    [idleHandler, isAnonymous]
  );

  useEffect(() => {
    AppState.addEventListener("change", appStateChangeHandler);

    return () => {
      AppState.removeEventListener("change", appStateChangeHandler);
    };
  }, [appStateChangeHandler]);

  useEffect(() => {
    if (appState.current || isAnonymous) return;
    idleHandler();
    appState.current = "active";
  }, [idleHandler, isAnonymous]);
};

export default useIdleSignOut;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment