【iOS】ローカル認証(LocalAuthentication / Touch ID / Face ID) についてのおさらい
今まで何となくでローカル認証(LocalAuthentication フレームワーク)を使用してしまっていた部分があるのですが、機会があったので、一通りのおさらいをしつつ、分かる範囲で設定可能なプロパティについて調べてみました。
環境
- Xcode 14.2
- iPhone XR
- iOS 16.3.1
また、動作確認用に簡単なアプリを以下に作成しています。
用語の確認
- ローカル認証
- LocalAuthentication フレームワークにより実行される、iOS 端末内に閉じた認証のこと。
- Web API 等を用いた外部のサーバへのログイン(=ネットワーク認証)ではない
- 生体認証、パスコード認証を含む。
- LocalAuthentication フレームワークにより実行される、iOS 端末内に閉じた認証のこと。
- 生体認証
- Touch ID、Face ID 等の生体情報(顔/指紋)を用いた認証の総称。
- パスコード認証
- Touch ID や Face ID が使用出来ない場合に行う、端末(iPhone)に設定済のパスコードを使用した認証。
- パスコード
- iOS 端末をロックするために設定する、通常は 6 桁や 4 桁の数字の番号。
- iCloud(Apple ID)のパスワードは全くの別物。
- 「PIN コード」と表現される場合もあるが、iOS の場合はSIM PINを指すことが多いようなので、パスコードと呼ぶ方が恐らく無難。
- Touch ID
- Apple が開発した指紋認証システム。
- Face ID
- Apple が開発した顔認証システム。
基本的な使い方
1. Face ID Usage Description (NSFaceIDUsageDescription) の設定
Face ID を使用するためにまず、ユーザへの説明文を Info.plist[1] に設定する必要があります。アプリでの Face ID を使用するのかという説明を簡潔に記述する必要があります。
ここで設定した文言は、ローカル認証(後述する evaluatePolicy(\_:localizedReason:reply:)
)を初めて実行するタイミングで表示されるダイアログに表示されます。
リファレンスはこちらにあります。
LAContext の作成
2. import およびimport LocalAuthentication
let context = LAContext()
3. 認証可能かどうかの確認
canEvaluatePolicy(_:error:)の呼び出し
// 引数に指定のポリシー(ここでは`.deviceOwnerAuthentication`)で認証可能かのテスト
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
// 指定のポリシーで認証不可のため、通常のユーザ名/パスワードログインに誘導する。
// ...
return
}
4. ローカル認証の実行
evaluatePolicy(_:localizedReason:reply:)の呼び出し
Task {
do {
try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Log in to your account")
state = .loggedin
} catch let error {
// 認証に失敗したため、通常のユーザ名/パスワードログインに誘導する。
// ユーザがキャンセルした場合もここを通るため、`error`の内容を見てエラーメッセージ等を表示するかどうかは判断する。
// ...
}
}
ポリシー設定について(LAPolicy)
LAPolicy列挙型にて、認証の方式を指定するためのポリシーが定義されています。
evaluatePolicy(\_:localizedReason:reply:)
の引数に指定することで、指定のポリシーに従って認証処理を実行します。
また、canEvaluatePolicy(\_:error:)
の引数に指定することで、指定の認証方式が現在利用可能かどうかを事前に確認することが出来ます。
iOS では現状、以下のポリシーが利用可能です。
以下については macOS 専用のため、iOS としては無関係です。
- deviceOwnerAuthenticationWithBiometricsOrWatch
- deviceOwnerAuthenticationWithWatch
- deviceOwnerAuthenticationWithWristDetection
deviceOwnerAuthentication
このポリシーを使用すると、iOS では生体認証もしくは端末のパスコード認証を行います。
システムはまず下記の条件を満たす場合、最初に生体認証を使用します。
- 端末が生体認証をサポートしている
- 生体情報(顔・指紋)が登録済
- ユーザにより生体認証が無効にされていない
生体認証が使用出来ない場合、または生体認証に複数回失敗した場合に表示されるフォールバックボタンをタップした時に端末のパスコードまたはユーザのパスワードの入力をユーザーに促します。
実際に手元の端末で以下の操作を試してみましたが、確かに Face ID ダイアログではなく端末のパスコード入力画面が表示されました。
- 初回実行時の確認ダイアログにて、「許可しない」を選択
- 端末の設定アプリで自アプリの設定に遷移 → 「Face ID」のチェックを外す
- 端末の Face ID をリセット
また、端末のパスコードがオフになっている場合、passcodeNotSetというエラーが返ってきます。
パスコード認証は 6 回失敗するとロックアウトされます。
(...と書いてあるのですが、手元の端末だと 4 回失敗した時点でロックアウトがされてしまう...何故?)
deviceOwnerAuthenticationWithBiometrics
こちらのポリシーは生体認証のみを行います。最初に生体認証の実行を試みるという点では同様ですが、前述のdeviceOwnerAuthentication
とは異なり、生体認証が使用出来ない場合にはエラーが返却されます。また生体認証に複数回失敗した場合にも当然ながらフォールバックされません。
エラーコードとしては、
- 生体認証が使用出来ない場合はbiometryNotAvailable
- 生体認証に複数回失敗した時はauthenticationFailed
が返却されているようです。
ただ、このポリシーを使用する場合でもパスコード設定自体は必要なようで、オフになっている場合は同様にpasscodeNotSet
エラーが返ってきます。
画面表示のカスタマイズ
前述のFace ID Usage Description
の他にも、LAContext の以下のプロパティで認証ダイアログのメッセージを変更出来ます。
localizedReason
Touch ID 認証時のダイアログや、パスコード認証時の画面に表示されるメッセージを設定します。
また Face ID の場合、認証時には Face ID アイコンが出るだけなので表示されませんが、フォールバック時のダイアログには表示されていますね。
localizedFallbackTitle
フォールバックボタンのタイトルを変更出来ます。
未設定(nil)の場合はデフォルトのタイトル(パスコード入力の場合は日本語環境の場合「パスコードを入力」)が表示されます。(何故か localizedReason の部分もデフォルトのメッセージになってますが...)
空文字("")が設定されると、フォールバックボタンが非表示になります。(localizedReason の部分も非表示になってますが...)
localizedCancelTitle
生体認証失敗時やフォールバック時のダイアログに表示されるキャンセルボタンのタイトルを変更出来ます。
未設定(nil)の場合はデフォルトのタイトル(日本語環境の場合「キャンセル」)が表示されます。
端末でサポートしている生体認証の種別の判定(LABiometryType)
LAContext.biometryTypeを参照すると、その端末で使用出来る生体認証の種類(Touch ID / Face ID)を知ることが出来ます。
返却される値はLABiometryType列挙型に定義されています。
ただし、この値を参照するには先にcanEvaluatePolicy(\_:error:)
を呼び出しておく必要があります。もし先に参照してしまった場合、none
が返却されます。
端末のロック解除状態の再利用
touchIDAuthenticationAllowableReuseDuration
生体認証を使って端末のロックを解除した後、認証状態が再利用される時間(秒)を指定します。
例えば10
を設定した場合、端末ロックを解除してから 10 秒間に呼び出されたevaluatePolicy(\_:localizedReason:reply:)
については生体認証ダイアログが表示されず、自動的に認証が成功するようになるため、ほぼ同時に複数回の認証が要求されるような事態を回避出来ます。
デフォルト値は0
で、この場合は認証が再利用されず、端末のロック解除の直後でも認証が再度要求されます。
ちなみにどうやら「生体認証を使用」して「端末のロックを解除」した場合に限るようです。以下の場合には秒数に関係なく即座に認証が要求されました。
- アプリ内で一度生体認証に成功した直後の場合
- 端末のロック解除にパスコード認証を使用した場合
ちなみに touchID...という名前ですが、Face ID の場合でも問題無く適用されました。
LATouchIDAuthenticationMaximumAllowableReuseDuration
touchIDAuthenticationAllowableReuseDuration
に設定可能な秒数の最大値です。これよりも長い秒数を指定することは出来ません。
ちなみにですが、手元の端末では300
となっていました。もちろん環境次第でこの値は変わるものと考えられるので参考程度に。
生体情報の更新検知
evaluatedPolicyDomainState
特に意味は無い単なる Data 型(opaque structure)ですが、一度この値を保存しておいて、また別のタイミングで取得した値と比較することで、認証情報(authorized database)の更新を検知出来るようです。
手元の端末では以下の操作を行った後、値が変更されていることを確認しました。
- 「Face ID をリセット」でリセット後、「Face ID をセットアップ」で新しい顔を登録
- 「もう一つの容姿をセットアップ」で新しい顔を追加登録
逆に以下の場合は変更されませんでした。
- パスコードの変更
- 「Face ID を使用するには注視が必要」のオフ
使い所が難しいですが、恐らく万が一、他の誰かに生体情報が登録されてしまった場合を考慮して、
- 初回認証時に UserDefaults 等に永続化しておく
- 次回の認証成功時に値が変更されていた場合はアプリ側で追加の認証(パスワードログイン)を要求する
- 追加の認証に成功した場合は新しい
evaluatedPolicyDomainState
を再度上書き保存する
といった使い方が考えられそうです。
この値はcanEvaluatePolicy(\_:error:)
の成功後か、evaluatePolicy(\_:localizedReason:reply:)
で生体認証に成功した後にのみ返却されます。フォールバックしてパスコードで認証した場合には返却されないため注意が必要です。
canEvaluatePolicy(\_:error:)
も生体認証に関連したポリシーを指定して成功した場合に限定されるようですが、現状は全て生体認証関連(deviceOwnerAuthentication / deviceOwnerAuthenticationWithBiometrics)なので、現時点では特に気にする必要は無いでしょう。
参考
-
もしくは Xcode13 以降に作成したプロジェクト等で Info.plist ファイルが無い場合、プロジェクト設定の
Info
タブやBuild Settings
タブのInfo.plist Values
に設定する ↩︎
Discussion