Avatar

Jambo, I'm Julia.

Biometric Authentication in React Native With Expo

#authentication #expo #react-native

4 min read

Here's a quick blog post on how to set up biometric authentication in your React Native + Expo app. Expo makes things super easy, so it's all pretty self explanatory really, but I wanted to write this blog post just to note down my process for someone using a "bare workflow".

Step 1: Install expo-local-authentication.

Check out the instructions here, but you basically need to run expo install expo-local-authentication. Depending on the Expo SDK / React Native version you're on, you might need to install the react-native-unimodules package as well.

If you've ejected from Expo, you'll need to explicitly state that you'll be using iOS's FaceID feature, along with the reason for using it. Add these lines (amended for your use case) to your Info.plist file. Android permissions should be set automatically.

<key>NSFaceIDUsageDescription</key>
<string>This app uses Face ID to authenticate the user for blah blah blah reasons.</string>

Step 2: Create your authentication screen

The expo-local-authentication package comes with some nice methods out of the box. The main ones I used were:

  • supportedAuthenticationTypesAsync which determine what kinds of authentications are available on the device
  • authenticateAsync which attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).

The logical steps were therefore to:

  • Call supportedAuthenticationTypesAsync upon the component screen mounting, to check what types of biometric authentication are available. This function returns an array of types, so I then looped through the results array and set the local state to either true or false, for facialRecognitionAvailable, fingerprintAvailable and irisAvailable.
  • If at least one biometric authentication method is available, show a button, which on press, calls a function called authenticate. The function basically tries to authenticate the user using authenticateAsync. The return value from this function is either "success" or "error", with the latter providing an error message. This can therefore be used to set and display the result variable for feedback to the user.

Side notes

  • It's not possible to define a specific biometric authentication method for the authenticateAsync function.
  • If neither of the biometric authentication methods work, the function uses fallback methods like device pin code / password.
  • If the error message comes back as unknown, a likely cause is that the user has tried authenticating too many times.
  • The use of the enum EResult here is entirely optional, but just organises the code a little more nicely. 🙂
import * as LocalAuthentication from 'expo-local-authentication';
import * as React from 'react';
// import your other components

enum EResult {
  CANCELLED = 'CANCELLED',
  DISABLED = 'DISABLED',
  ERROR = 'ERROR',
  SUCCESS = 'SUCCESS',
}

export function BiometricAuthScreen() {
  const [facialRecognitionAvailable, setFacialRecognitionAvailable] = React.useState(false);
  const [fingerprintAvailable, setFingerprintAvailable] = React.useState(false);
  const [irisAvailable, setIrisAvailable] = React.useState(false);
  const [loading, setLoading] = React.useState(false);
  const [result, setResult] = React.useState<EResult>();

  const checkSupportedAuthentication = async () => {
    const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
    if (types && types.length) {
      setFacialRecognitionAvailable(types.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION));
      setFingerprintAvailable(types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT));
      setIrisAvailable(types.includes(LocalAuthentication.AuthenticationType.IRIS));
    }
  };

  const authenticate = async () => {
    if (loading) {
      return;
    }

    setLoading(true);

    try {
      const results = await LocalAuthentication.authenticateAsync();

      if (results.success) {
        setResult(EResult.SUCCESS);
      } else if (results.error === 'unknown') {
        setResult(EResult.DISABLED);
      } else if (
        results.error === 'user_cancel' ||
        results.error === 'system_cancel' ||
        results.error === 'app_cancel'
      ) {
        setResult(EResult.CANCELLED);
      }
    } catch (error) {
      setResult(EResult.ERROR);
    }

    setLoading(false);
  };

  React.useEffect(() => {
    checkSupportedAuthentication();
  }, []);

  let resultMessage;
  switch (result) {
    case EResult.CANCELLED:
      resultMessage = 'Authentication process has been cancelled';
      break;
    case EResult.DISABLED:
      resultMessage = 'Biometric authentication has been disabled';
      break;
    case EResult.ERROR:
      resultMessage = 'There was an error in authentication';
      break;
    case EResult.SUCCESS:
      resultMessage = 'Successfully authenticated';
      break;
    default:
      resultMessage = '';
      break;
  }

  let description;
  if (facialRecognitionAvailable && fingerprintAvailable && irisAvailable) {
    description = 'Authenticate with Face ID, touch ID or iris ID';
  } else if (facialRecognitionAvailable && fingerprintAvailable) {
    description = 'Authenticate with Face ID or touch ID';
  } else if (facialRecognitionAvailable && irisAvailable) {
    description = 'Authenticate with Face ID or iris ID';
  } else if (fingerprintAvailable && irisAvailable) {
    description = 'Authenticate with touch ID or iris ID';
  } else if (facialRecognitionAvailable) {
    description = 'Authenticate with Face ID';
  } else if (fingerprintAvailable) {
    description = 'Authenticate with touch ID ';
  } else if (irisAvailable) {
    description = 'Authenticate with iris ID';
  } else {
    description = 'No biometric authentication methods available';
  }

  return (
    <Screen>
      <Text>
        {description}
      </Text>
      {facialRecognitionAvailable || fingerprintAvailable || irisAvailable ? (
        <Button onPress={authenticate}>
          Authenticate
        </Button>
      ) : null}
      {resultMessage ? <Text>{resultMessage}</Text> : null}
    </Screen>
  );
}

Web Mentions

Tweet about this post and have it show up here!

© 2016-2022 Julia Tan · Powered by Next JS.