ReactNative+Expo LocalAuthenticationで指紋認証を試してみた

## 検証環境
|  | バージョン |
|:-----------|------------:|
| React    | 16.11.0      |
| ReactNative    | 0.62.2      |
| Expo    | 3.24.0      |
| Android    | Pie      |

## 指紋認証・顔認証について

### Android
BiometricPrompt APIを使用する。
[生体認証システム](https://source.android.com/security/biometric)
[生体認証ダイアログを表示する-Androidデベロッパードキュメント](https://developer.android.com/training/sign-in/biometric-auth)
Android10~顔認証をサポートしている。
指紋認証はAndroid6.0~。

>Android 10
> デベロッパーが生体認証の利用可否をクエリできる BiometricManager クラスを導入しています。
> BiometricPrompt 向けに指紋認証と顔認証を統合しています。
> Android 9
> BiometricPrompt 向けにのみ指紋認証を統合しています。

参考:[【Android】BiometricPromptと指紋認証機能の実装方法について](https://qiita.com/hotdrop_77/items/75fa88fbcb1594338047)


### iOS
TouchIDとFaceIDを使用する。
FaceID対応はiPhone X以上。
[Face ID に対応している iPhone と iPad のモデル](https://support.apple.com/ja-jp/HT209183)
[Local Authentication-Apple Developer Documentation](https://developer.apple.com/documentation/localauthentication/)
[Logging a User into Your App with Face ID or Touch ID](https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id)


## Expoで使用する

### [Expoドキュメント](https://docs.expo.io/versions/latest/sdk/local-authentication/)

### [GitHub](https://github.com/expo/expo/tree/master/packages/expo-local-authentication)

### [Expo Snackサンプル](https://snack.expo.io/@charliecruzan/localauthentication35example)

## インストール

```sh
 expo init
 expo install expo-local-authentication
 expo start
```

## サンプルコード
```react
 import React from "react";
 import { StyleSheet, Text, View, Button } from "react-native";
 import * as LocalAuthentication from "expo-local-authentication";
 
 export default class App extends React.Component {
   supportAuthentication = async () => {
     const result = await LocalAuthentication.supportedAuthenticationTypesAsync();
     alert(result);
   };
 
   checkDeviceForHardware = async () => {
     const compatible = await LocalAuthentication.hasHardwareAsync();
     if (compatible) {
       alert("有効なデバイスです");
     } else {
       alert("無効なデバイスです");
     }
   };
 
   checkForBiometrics = async () => {
     const biometricRecords = await LocalAuthentication.isEnrolledAsync();
     if (biometricRecords) {
       alert("生体認証有効");
     } else {
       alert("生体認証無効");
     }
   };
 
   handleAuthentication = async () => {
     const result = await LocalAuthentication.authenticateAsync({
       promptMessage: "認証を促すメッセージ",
       cancelLabel: "キャンセルラベル",
       fallbackLabel: "認証失敗時のメッセージ",
       disableDeviceFallback: false,
     });
     if (result.success) {
       alert("認証成功");
     } else {
       LocalAuthentication.cancelAuthenticate();
       alert("認証失敗");
     }
   };
 
   render() {
     return (
       <View style={styles.container}>
         <Text>Local authentication</Text>
         <Button
           title="デバイスチェック"
           onPress={this.checkDeviceForHardware}
         />
         <Button title="生体認証チェック" onPress={this.checkForBiometrics} />
         <Button
           title="認証サポートチェック"
           onPress={this.supportAuthentication}
         />
         <Button title="認証チェック" onPress={this.handleAuthentication} />
       </View>
     );
   }
 }
 
 const styles = StyleSheet.create({
   container: {
     flex: 1,
     backgroundColor: "#fff",
     alignItems: "center",
     justifyContent: "center",
   },
 });
```

## [LocalAuthentication API](https://docs.expo.io/versions/latest/sdk/local-authentication/)

### [LocalAuthentication.hasHardwareAsync()](https://docs.expo.io/versions/latest/sdk/local-authentication/#localauthenticationhashardwareasync)
デバイスで顔認証もしくは指紋認証が使用できるかどうか。

### [LocalAuthentication.supportedAuthenticationTypesAsync()](https://docs.expo.io/versions/latest/sdk/local-authentication/#localauthenticationsupportedauthenticationtypesasync)
デバイスがサポートしている認証タイプを取得する。

```react
 export declare enum AuthenticationType {
     FINGERPRINT = 1,
     FACIAL_RECOGNITION = 2,
     IRIS = 3
 }
```

返り値が`1`は指紋認証、`2`は顔認証を意味する。
デバイスがどちらの認証方法にも対応している場合、配列に両方含まれた`[1,2]`。
どちらにも対応していない場合空配列が返る。

### [LocalAuthentication.isEnrolledAsync()](https://docs.expo.io/versions/latest/sdk/local-authentication/#localauthenticationisenrolledasync)
認証に使用する顔データもしくは指紋データが保存されているか。

### [LocalAuthentication.authenticateAsync(options)](https://docs.expo.io/versions/latest/sdk/local-authentication/#localauthenticationauthenticateasyncoptions)
指紋認証・顔認証を実行する。

指紋認証・顔認証データが存在しない場合falseになります。

```react
{
   "error": "not_enrolled",
   "message": "KeyguardManager#isDeviceSecure() returned false",
   "success": false,
 }
```

認証がキャンセルされた場合。

```react
{
   "error": "user_cancel",
   "message": "指紋の操作がユーザーによりキャンセルされました。",
   "success": false,
 }
```

認証に成功した場合`success`が返ります。

```react
{
   "success": true,
 }
```

#### optionについて

```js
{
  promptMessage: "認証を促すメッセージ",
  cancelLabel: "キャンセルラベル",
  fallbackLabel: "認証失敗時のメッセージ",
  disableDeviceFallback: false,
}
```
 
##### promptMessage (string) 
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/18156015-4196-b421-ed29-a553265f32bf.png" width=30%>
上記画像の"Authenticate"のテキストを変更する。

##### cancelLabel (string)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/60a61a94-eaee-7fd3-971a-6dcfa19306f5.png" width=30%>
disableDeviceFallbackオプションがtrueのときキャンセルボタンのテキストを変更する。

##### fallbackLabel (string) 

認証に何度か失敗した後に表示される既定の Use Passcode ラベルをカスタマイズする(iOSのみ)。

##### disableDeviceFallback (boolean)

デフォルト(false)の場合、認証に複数回失敗したとき既定の方法(PINコード・パターンなど)で代替の認証を表示する。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/9216c17b-767c-fd01-64f6-42fedaa2b75d.gif" width=30%>

trueに設定した場合、認証に複数回失敗しても別の認証方法を表示しない。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/658122/e8550113-a619-0b25-15e4-608047be4ad3.gif" width=30%>

### [LocalAuthentication.cancelAuthenticate()](https://docs.expo.io/versions/latest/sdk/local-authentication/#localauthenticationcancelauthenticate)

認証をキャンセルする(androidのみ)。