Live Activitiesを実装したあと、アプリ内に保存している個人的なデータをもう少し保護したくなり、起動時にFace ID/Touch IDでのロックを追加してみることにしました。
1. シミュレーターでは正しく動作確認できなかった
最初はシミュレーターでLocalAuthenticationの動作確認をしようとしたのですが、実際の生体情報がないため挙動がよく分からず戸惑いました。
シミュレーターには「顔認証が成功した/失敗した」を疑似的に発生させるメニューがあり、それを使えば一応テストはできるものの、実機での自然な認証フローとは体感が大きく異なりました。最終的な確認は実機で行うのが確実だと分かりました。
2. Face IDとTouch IDを区別せず1つの分岐で済むと思っていた
はじめは「Face IDならこう、Touch IDならこう」と機種ごとに条件分岐が必要だと思い込んでいました。
let context = LAContext()
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
実際にはLAContextが生体認証の種類を意識させずに抽象化してくれるため、Face IDかTouch IDかで実装を分ける必要はありませんでした。context.biometryTypeで種類を取得できますが、認証処理自体は共通のAPIで完結します。
3. 生体認証を拒否されたときのフォールバックを用意していなかった
実装したばかりの頃、ユーザーが生体認証をキャンセルしたり、複数回失敗してロックされたりした場合の分岐を用意しておらず、アプリがそのまま固まったように見える不具合がありました。
.deviceOwnerAuthenticationWithBiometrics
このポリシーだと生体認証のみが対象になり、失敗時にパスコード入力へ自動でフォールバックしません。
.deviceOwnerAuthentication
こちらのポリシーに変更すると、生体認証が使えない・失敗した場合に端末のパスコード入力へ自動的にフォールバックしてくれます。用途に応じてどちらのポリシーを使うか意識する必要があると学びました。
4. 認証結果のクロージャがメインスレッドで呼ばれないことに気づかなかった
認証成功後にすぐUIを更新するコードを書いたところ、まれにクラッシュしたり画面更新が反映されなかったりする不具合が発生しました。
evaluatePolicyの完了クロージャはバックグラウンドスレッドで呼ばれることがあるため、UI更新はメインスレッドに戻してから行う必要がありました。DispatchQueue.main.asyncでUI更新処理をラップすることで解決しました。
まとめ
- シミュレーターでの生体認証確認には限界がある。最終確認は実機で行う
LAContextは機種差を吸収してくれるため、Face ID/Touch IDを個別に分岐する必要はない- パスコードへのフォールバックが必要かどうかで
.deviceOwnerAuthenticationWithBiometricsと.deviceOwnerAuthenticationを使い分ける - 認証結果のクロージャはメインスレッドで呼ばれるとは限らないため、UI更新は
DispatchQueue.main.asyncで明示的にメインスレッドに戻す
「生体認証を付ければセキュリティが上がる」という単純な話ではなく、失敗時のユーザー体験まで含めて設計する必要があると実感しました。
次は、Siri Shortcuts(App Intents)を実装してみて分かったことをまとめます。
本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。


コメント