branch10480’s blog

Topics that I learned.

LocalAuthentication についてまとめ

こんにちは! 世田谷で iOS エンジニアをしてますブランチ(@branch10480)です。

今回は指紋認証・顔認証などの生体認証を使った認証を実装できる LocalAuthentication について説明しようと思います。

私が作ったサンプルコードはこちら↓

github.com

何ができるの?

指紋認証である Touch ID, または顔認証である Face ID で認証させる仕組みを組み込むことができるのです。こんな感じ↓

https://d2l930y2yx77uc.cloudfront.net/production/uploads/images/19001060/picture_pc_97a7e72a13a078619b312e57c171c055.gif

早速実装してみる!

まず、生体認証の仕組みを使う上でなぜ使うのかという説明を追加します。 この説明を記述しないと生体認証の仕組みを使うことはできません。

In any project that uses biometrics, include the NSFaceIDUsageDescription key in your app’s Info.plist file. Without this key, the system won’t allow your app to use Face ID.

<key>NSFaceIDUsageDescription</key>
<string>ログインを簡略化するために使用します。</string>

この説明は何に使われるかというと、Touch ID, もしくは Face ID を初めて使用するときに表示されます。

https://d2l930y2yx77uc.cloudfront.net/production/uploads/images/19001193/picture_pc_4ffa1b64ac4ddb0528a0000867e1c037.jpeg

Context を作成、設定する

var context = LAContext()
context.localizedCancelTitle = "Enter Username/Password"

Context はアプリと生体認証を管理している Secure Enclave(セキュリティプロセッサー)との仲介役のこと。 2行目では生体認証を使わない選択肢のタイトルを指定できます。

テストポリシーが使えるかを判断する

var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) 

LAPolicy enum でテストポリシーを指定できますが、そもそもそのポリシーが使える端末なのかをここでチェックします。

LAPolicy.deviceOwnerAuthentication

パスコード入力に戻ることを許す

LAPolicy.deviceOwnerAuthenticationWithBiometrics

パスコード入力に戻ることを許さない

テストポリシーを評価する

上記チェックが正常に終われば評価が可能です! 実際に認証開始します。

let reason = "Log in to your account"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, error in

   if success {

       // Move to the main thread because a state update triggers UI changes.
       DispatchQueue.main.async { [unowned self] in
           // UI変更など、成功時の処理
       }

   } else {
       print(error?.localizedDescription ?? "Failed to authenticate")

       // ユーザー名とパスワードの入力を求める
   }
}

Touch ID と パスコード入力のときは OS がメソッドで指定したメッセージを表示します。このメッセージはローカライズされていることが重要だとドキュメントでは主張されています。

It’s important to provide a clear explanation, localized for any regions in which you operate, of why your app is asking the user to authenticate.

FaceID 用に UI を調整する

Touch ID と Face ID ではUI上の進行フェーズに違いがあります。 Touch ID では指紋認証をする前の段階で確認画面が表示され、そこでユーザーが生体認証をやめようと思えばキャンセルされます。一方、Face ID の場合はすぐさま顔認証を施行します。

このUI上の振る舞いを調整するには、Face ID を使う端末に関しては予め説明をUIに記載しておくことで対応できます。

また、この Face ID を使う端末か については LAContext.biometryType パラメータで確認ができますが、このパラメータが有効になるのは canEvaluatePolicy(_:error:) メソッドが呼ばれてからになりますので、予め呼んでおきましょう。

コード全体だと以下の様になります。

//
//  ViewController.swift
//  LocalAuthenticationSample
//

import UIKit
import LocalAuthentication

class ViewController: UIViewController {
   
   @IBOutlet weak var headerView: UIView!
   @IBOutlet weak var loginStatusLabel: UILabel!
   @IBOutlet weak var button: UIButton!
   @IBOutlet weak var faceIdDescriptionLabel: UILabel!
   
   enum AuthenticationState {
       case loggedin, loggedout
   }
   var state: AuthenticationState = .loggedout {
       didSet {
           headerView.backgroundColor = self.state == .loggedin ? .systemGreen : .systemGray
           loginStatusLabel.text = self.state == .loggedin ? "ログイン中" : "LocalAuthenticationSample"
           button.backgroundColor = self.state == .loggedout ? .systemGreen : .systemGray
           button.setTitle(self.state == .loggedin ? "ログアウト" : "ログイン", for: .normal)
           faceIdDescriptionLabel.isHidden = state == .loggedin || context.biometryType != .faceID
       }
   }
   var context: LAContext = LAContext()

   override func viewDidLoad() {
       super.viewDidLoad()
       // これを実行しないと context.biometryType が有効にならないので一度実行
       context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
       state = .loggedout
   }

   @IBAction func didTabLoginButton(_ sender: Any) {
       
       guard state == .loggedout else {
           state = .loggedout
           return
       }
       
       context = LAContext()
       if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) {
           
           let reason = "パスワードを入力してください"
           context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
               if success {
                   DispatchQueue.main.async { [unowned self] in
                       self.state = .loggedin
                   }
               } else if let laError = error as? LAError {
                   switch laError.code {
                   case .authenticationFailed:
                       break
                   case .userCancel:
                       break
                   case .userFallback:
                       break
                   case .systemCancel:
                       break
                   case .passcodeNotSet:
                       break
                   case .touchIDNotAvailable:
                       break
                   case .touchIDNotEnrolled:
                       break
                   case .touchIDLockout:
                       break
                   case .appCancel:
                       break
                   case .invalidContext:
                       break
                   case .notInteractive:
                       break
                   @unknown default:
                       break
                   }
               }
           }
       } else {
           // 生体認証ができない場合の認証画面表示など
       }
   }
}

まとめ

LocalAuthentication はその名の通り、手元の端末上での認証の機能です。そのため、メインの認証には使えません。

しかし、ユーザー名、パスワードを入力させるなどのサービスでログイン時間が短いサービスの場合などは、その情報を端末内に保存して LocalAuthentication が成功した場合は保存した情報でログインさせるということができるようになります。

ユーザーにとっては入力の手間がなくなるので、よりよいユーザー体験を得られますね。使えるところは使っていきましょう!

参考

https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id