В этом посте мы рассмотрим интеграцию Face ID в приложение React Native, создание пары ключей RSA 2048, создание подписи RSA-SHA256 и создание сервера NodeJS для проверки подписи Face ID и открытого ключа.

Face ID стал широко используемой функцией безопасности в приложениях для iOS и Android. Эта функция позволяет пользователям испытать простой и безопасный процесс аутентификации при использовании вашего приложения.

Мы будем интегрировать аутентификацию Face ID в приложение React Native, используя пакет react-native-biometrics.

Предпосылки

Чтобы следить за постом, вам понадобится приложение React Native, которое можно легко создать с помощью этой команды:

npx react-native init RealApp

В этом примере мы будем использовать один из предварительно встроенных экранов входа WithFrame с действием Face ID.

Монтаж

Если вы используете пряжу, выполните следующую команду:

yarn add react-native-biometrics

Если вы используете NPM, выполните следующую команду:

npm install react-native-biometrics --save

Также не забываем про линковку нативных пакетов:

npx pod-install

В React Native 0.60+ функция автоссылки CLI связывает модуль при создании приложения.

Разрешения

После завершения установки нам нужно будет добавить строки разрешений для iOS и Android.

Для Android вам нужно будет добавить в файл AndroidManifest.xml следующее:

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

Для iOS вам нужно будет добавить в файл Info.plist следующее:

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

Шаг 1. Создайте пару биометрических ключей.

На экране входа в систему у нас есть две кнопки: «Войти» и «Face ID». После проверки учетных данных пользователя мы спросим его, не хотят ли они использовать функцию Face ID в следующий раз.

Конечно, сначала нам нужно будет проверить, доступен ли Face ID на устройстве, используя метод isSensorAvailable().

Как только publicKey получен методом createKeys(), мы должны отправить его на сервер и сохранить в сущности пользователя. Позже мы воспользуемся им для проверки подписи.

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>

Шаг 2. Подтвердите биометрическую подпись

Теперь, когда у нас есть publicKey, хранящийся в сущности пользователя, мы можем использовать его для проверки аутентификации пользователя.

<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>

Шаг 3: Проверка подписи с помощью открытого ключа в NodeJS

После того, как пользователю будет предложено пройти аутентификацию Face ID, Apple извлечет закрытый ключ из хранилища ключей, а затем использует его для создания подписи RSA PKCS#1v1.5 SHA 256.

Ранее мы сохранили открытый ключ на объекте пользователя, и теперь мы можем использовать его для проверки того, что подпись была подписана с использованием закрытого ключа из той же пары открытый/закрытый ключ. В NodeJS это можно сделать с помощью модуля crypto.

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',
  });
});

Мы надеемся, что вам понравился этот пост, и теперь вы лучше понимаете, как интегрировать Face ID в ваше приложение React Native.

Окончательный код приложения React Native можно найти в нашем репозитории GitHub.

Этот экран входа и многие другие можно найти на нашем сайте: WithFrame React Native Components