【SwiftUI】生体認証(Face ID/Touch ID)の機能を実装してみる

# 生体認証(Face ID/Touch ID)とは？
### Face ID
iPhone Xから導入された、顔を使って認証を行う機能のこと。
アップルの資料によれば100万分の1の確率で、赤の他人がたまたま本人とみなされることがあるそうです。

### Touch ID
Apple のデバイスにおける、指紋を使って認証を行う機能のこと。
iPhone 5S 以降で利用可能になりました。が、最近はFace IDが主流になっています。

ちなみにたまたま指紋の一部分が似ていて、同じ指紋であると認識される確率は、 アップルの資料によればおよそ5万分の1らしいです。

# 開発環境
- Xcode：15.1.0
- MacOS：13.5.1
- Interface：SwiftUI

**Face ID端末**
 - iOS：17.2.1
 - iPhone 15 Pro

**Touch ID端末**
 - iOS：15.7.3
 - iPhone 6S

 ## プロジェクトの設定
まずは `info.plist`にPrivacy - Face ID Usage Descriptionを追加する必要があります。
これはアプリが生体認証を使用する目的をユーザーに説明するために使われ、これを設定していないとアプリが後でクラッシュします。
![スクリーンショット 2023-12-22 0.45.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604036/10c27d88-6940-69d2-e57e-19a03961ebab.png)

## LocalAuthenticationのImport
LocalAuthenticationフレームワークをインポートしましょう。
```swift:ContentView.swift
import LocalAuthentication
```

## 認証の処理
今回は認証が成功した場合は画面に「認証成功！」と表示し、失敗した場合はアラートを表示させる簡単なレイアウトを組んでいます。
`LAContext`は認証ポリシーやアクセス制御を評価するためのもので、`.canEvaluatePolicy` 関数で 認証が可能かどうかチェックし、Face ID/Touch IDがない端末はここで弾かれます。
```swift:ContentView.swift
import SwiftUI
import LocalAuthentication

struct ContentView: View {
    @State private var isUnlocked = false

    var body: some View {
        NavigationView {
            VStack {
                Spacer()

                if isUnlocked {
                    Text("認証成功！")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                } else {
                    Text("Face ID/Touch IDでロック解除")
                        .font(.headline)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                        .onTapGesture {
                            authenticate()
                        }
                }

                Spacer()

                if isUnlocked {
                    Button(action: authenticate) {
                        Text("もう一度認証する")
                            .font(.headline)
                            .padding()
                            .frame(maxWidth: .infinity)
                            .background(Color.orange)
                            .foregroundColor(.white)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                    }
                    .padding()
                }
            }
            .navigationTitle("生体認証デモ")
        }
    }

    func authenticate() {
        let context = LAContext()
        var error: NSError?

        // 生体認証（Face IDやTouch ID）が利用可能であるかどうかを確認
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            let description = "認証が必要です。"

            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: description) { success, authenticationError in
                DispatchQueue.main.async {
                    if success {
                        self.isUnlocked = true
                    } else {
                        self.isUnlocked = false
                    }
                }
            }
        }
    }
}
```

## 完成したもの
**Face IDの端末**
![RPReplay_Final1703176370.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604036/c7c9db76-c6c9-50c1-dea6-e61a5becda53.gif)

**Touch IDの端末**
![RPReplay_Final1703176751.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604036/f77d4f7e-e3ee-4c23-e767-95d4b7febf4b.gif)