Face ID Authentication

Step-by-Step Guide to Implementing Face ID Authentication in Your React Native Application

In this tutorial we will cover Face ID integration in the React Native app, creating an RSA 2048 key pair, generating an RSA-SHA256 signature, and building a NodeJS server to verify the Face ID signature and the public key.

Intro

Face ID has become a widely used security feature in iOS & Android applications. This feature allows users to experience a seamless and secure authentication process when using your app.

We will be integrating Face ID authentication into a React Native application using react-native-biometrics package.

Prerequisites

To follow the post, you will need a React Native application, which can be easily created using this command:

npx react-native init RealApp

Pre-built Sign In Screen with the Face ID action

For this example we will be using one of the WithFrame's pre-built sign in screens with the Face ID action.

Installation

If you are using yarn, run the following command:

yarn add react-native-biometrics

If you are using NPM, run the following command:

npm install react-native-biometrics --save

Also, let's not forget about linking the native packages:

npx pod-install

On React Native 0.60+ the CLI autolink feature links the module while building the app.

Permissions

After the installation is complete, we will need to add permissions strings for both iOS and Android.

For Android, you will need to add the following to your AndroidManifest.xml file:

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

For iOS, you will need to add the following to your Info.plist file:

<key>NSFaceIDUsageDescription</key>
<string>Enabling Face ID allows you quick access to RealApp</string>

Step 1: Create a biometric key pair

On our sign-in screen, we have two buttons, “Sign in” and “Face ID”. After a user's credentials are verified, we'll ask them if they would like to use a Face ID feature the next time.

Of course, we will have to check if the Face ID is available on the device first by using isSensorAvailable() method.

As soon as the publicKey is obtained using the createKeys() method, we should send it to the server and save it on the user's entity. We will later use it to verify the signature.

import ReactNativeBiometrics, { BiometryTypes } from "react-native-biometrics";
<TouchableOpacity
  onPress={async () => {
    // Verify user credentials before asking them to enable Face ID
    const { userId } = await verifyUserCredentials();
    const rnBiometrics = new ReactNativeBiometrics();
    const { available, biometryType } = await rnBiometrics.isSensorAvailable();
    if (available && biometryType === BiometryTypes.FaceID) {
      Alert.alert(
        "Face ID",
        "Would you like to enable Face ID authentication for the next time?",
        [
          {
            text: "Yes please",
            onPress: async () => {
              const { publicKey } = await rnBiometrics.createKeys();
              // `publicKey` has to be saved on the user's entity in the database
              await sendPublicKeyToServer({ userId, publicKey });
              // save `userId` in the local storage to use it during Face ID authentication
              await AsyncStorage.setItem("userId", userId);
            },
          },
          { text: "Cancel", style: "cancel" },
        ]
      );
    }
  }}
>
  <View style={styles.btn}>
    <Text style={styles.btnText}>Sign in</Text>
  </View>
</TouchableOpacity>

Step 2: Verify biometric signature

Now that we have a publicKey stored on a user's entity, we can use it to verify the user authentication.

<TouchableOpacity
  onPress={async () => {
    const rnBiometrics = new ReactNativeBiometrics();
    const { available, biometryType } = await rnBiometrics.isSensorAvailable();
    if (!available || biometryType !== BiometryTypes.FaceID) {
      Alert.alert("Oops!", "Face ID is not available on this device.");
      return;
    }
    const userId = await AsyncStorage.getItem("userId");
    if (!userId) {
      Alert.alert(
        "Oops!",
        "You have to sign in using your credentials first to enable Face ID."
      );
      return;
    }
    const timestamp = Math.round(new Date().getTime() / 1000).toString();
    const payload = `${userId}__${timestamp}`;
    const { success, signature } = await rnBiometrics.createSignature({
      promptMessage: "Sign in",
      payload,
    });
    if (!success) {
      Alert.alert(
        "Oops!",
        "Something went wrong during authentication with Face ID. Please try again."
      );
      return;
    }
    const { status, message } = await verifySignatureWithServer({
      signature,
      payload,
    });
    if (status !== "success") {
      Alert.alert("Oops!", message);
      return;
    }
    Alert.alert("Success!", "You are successfully authenticated!");
  }}
>
  <View style={styles.btnSecondary}>
    <MaterialCommunityIcons
      color="#000"
      name="face-recognition"
      size={22}
      style={{ marginRight: 12 }}
    />
    <Text style={styles.btnSecondaryText}>Face ID</Text>
    <View style={{ width: 34 }} />
  </View>
</TouchableOpacity>

Step 3: Verifying the signature with the public key in NodeJS

After the user is prompted for their Face ID authentication, Apple will retrieve the private key from the keystore, and then use it to generate an RSA PKCS#1v1.5 SHA 256 signature.

Earlier, we saved a public key on the user's entity, and now we can use it to verify that the signature was signed using the private key from the same public/private key pair. In NodeJS, this can be accomplished using the crypto module.

const express = require("express");
const bodyParser = require("body-parser");
const crypto = require("crypto");
const app = express();
app.use(bodyParser.json({ type: "application/json" }));
app.post("/", async (req, res) => {
  const { signature, payload } = req.body;
  const userId = payload.split("__")[0];
  const user = await getUserFromDatabaseByUserId(userId);
  if (!user) {
    throw new Error("Something went wrong during your Face ID authentication.");
  }
  // this is the public key that was saved earlier
  const { publicKey } = user;
  const verifier = crypto.createVerify("RSA-SHA256");
  verifier.update(payload);
  const isVerified = verifier.verify(
    `-----BEGIN PUBLIC KEY-----\n${publicKey}\n-----END PUBLIC KEY-----`,
    signature,
    "base64"
  );
  if (!isVerified) {
    return res.status(400).json({
      status: "failed",
      message: "Unfortunetely we could not verify your Face ID authentication",
    });
  }
  return res.status(200).json({
    status: "success",
  });
});

We hope you enjoyed this tutorial and now have a better understanding on how to integrate Face ID into your React Native application.

Final React Native application code can be found in our GitHub repo