アプリ内での Touch ID を利用したユーザ認証

# はじめに
- モバイルデバイスにとって、認証は重要です（モバイルじゃなくてもだけど）
- iOS デバイスには、 Touch ID という指紋認証機能が組み込まれています
- Touch ID は、意外なほど簡単にアプリから利用できます

# 検証環境
- Xcode 8.3.3
- Swift 3.1
- iOS 10.3

# 利用する機能
## LocalAuthentication フレームワーク
### LAContext クラス
- このクラスを利用して、認証を行います
- Touch ID 搭載されているデバイスでは Touch ID（または Passcode）を、搭載されていないデバイスでは Passcode で認証を行います

| メソッド | 説明 |
|:--|:--|
| canEvaluatePolicy(_: error:) -> Bool | Touch ID が搭載されていて有効な状態、または Passcode が設定されている状態（Touch ID がなくても）であれば true を返す。 |
| evaluatePolicy(_: localizedReason: reply:) | Touch ID または Passcode を利用した認証を実行する。認証結果に対する処理は、第3引数のコールバックとして指定する。 |

###　LAPolicy 列挙型
- 上記のメソッドに渡す定数が定義されています
- iOS 9 以降であれば、後者の deviceOwnerAuthentication を利用します

| 定数 | 説明 |
|:--|:--|
| deviceOwnerAuthenticationWithBiometrics | iOS 8　で利用。 **iOS 9 以降でこの定数を利用すると、フォールバックボタン（指紋が認証されなかった場合に Passcode 入力を促すボタン）を押しても、入力画面が出ない？**[^1] |
| deviceOwnerAuthentication | iOS 9 以降で利用。 |

[^1]: Apple の公式ドキュメント上では、この挙動に関する記述が見つけられませんでした。ご存知の方がいらっしゃれば、ご指摘頂けるとうれしいです。また、手元に iOS 8 の載ったデバイスがなかったので、 iOS 8 での確認ができてません。

# 実装例
```swift:ViewController.swift
import UIKit
import LocalAuthentication

class ViewController: UIViewController {
    @IBOutlet weak var authResultLabel: UILabel!
    
    @IBAction func authenticate(_ sender: UIButton) {
        let myContext = LAContext()
        self.configure(context: myContext)
        let reason = "Only device owner can use this feature."

        var authError: NSError? = nil

        // Touch ID が有効または Passcode がセットされている状態であることを確認
        if myContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authError) {
            // 認証を実行
            myContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { (success, evaluateError) in
                if success {
                    print("Success")

                    DispatchQueue.main.async {
                        self.authResultLabel.text = "Success"
                    }
                } else {
                    let error = evaluateError! as NSError
                    let errorMessage = "\(error.code): \(error.localizedDescription)"

                    print(errorMessage)

                    DispatchQueue.main.async {
                        self.authResultLabel.text = errorMessage
                    }
                }
            }
        } else {
            // Touch ID と Passcode のいずれも有効ではない場合

            let errorMessage = "\(authError!.code): \(authError!.localizedDescription)"
            print(errorMessage)

            DispatchQueue.main.async {
                self.authResultLabel.text = errorMessage
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    /// ボタンの文字列をカスタマイズ
    private func configure(context: LAContext) {
        context.localizedCancelTitle = "キャンセル"
        context.localizedFallbackTitle = "パスコードを入力"
    }
}
```

# 実行結果
## 初期状態（Authenticate ボタンを押すと、指紋認証を実行）
<img width="414" alt="initial.png" src="https://qiita-image-store.s3.amazonaws.com/0/100115/79e0f8ad-3adc-d317-373d-c5dba3cd83b8.png">

## 指紋認証
<img width="414" alt="touch_id.png" src="https://qiita-image-store.s3.amazonaws.com/0/100115/20d3cad0-7fe2-377e-a81e-58e771f26cb3.png">

## 指紋認証失敗（パスコード入力を促すボタン表示）
<img width="414" alt="try_again.png" src="https://qiita-image-store.s3.amazonaws.com/0/100115/b3794270-4b3a-b5fc-0655-dce8dea3bcfa.png">

## パスコード入力
<img width="414" alt="passcode.png" src="https://qiita-image-store.s3.amazonaws.com/0/100115/cc0c473f-beb5-5e93-88e9-95ae60eab363.png">

## 成功時
<img width="414" alt="success.png" src="https://qiita-image-store.s3.amazonaws.com/0/100115/96ba1497-04cc-8054-2df9-464d90843dd6.png">

## キャンセル時
<img width="414" alt="cancelled.png" src="https://qiita-image-store.s3.amazonaws.com/0/100115/cb1699c7-957c-280a-0474-b2e6de97af01.png">

## パスコードが設定されていない場合
![no_passcode.png](https://qiita-image-store.s3.amazonaws.com/0/100115/6bd139b2-0d14-b30c-ff31-7b6838b64322.png)

# サンプルプロジェクト
- 今回作成したサンプルは、 GitHub からダウンロードできます。
    - [aokiplayer/LocalAuthSample: iOS app sample of authorization using Touch ID.](https://github.com/aokiplayer/LocalAuthSample)

# 参考サイト
- [ App-Level Passcode | Cocoanetics](https://www.cocoanetics.com/2016/03/app-level-passcode/)
    - iOS 9 以降では deviceOwnerAuthentication を利用する旨の解説あり
- [LAContext - LocalAuthentication | Apple Developer Documentation](https://developer.apple.com/documentation/localauthentication)
    - 公式ドキュメント（情報が古い？）

----