NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

【Swift】LocalAuthenticationを使用して生体認証を試してみた

概要

ロック画面の解除に普段何気なく使用しているFace IDやTouch IDですが、セキュリティレベルを高めるのはもちろん、ユーザーがIDやパスワードを打つストレスの解消にもなっています。
私を含め、多くの人は一度体験したらもう二度とパスワードを入力して画面ロックを解除する時代に戻れないのでは?と思うほどの印象です。そんな便利なFace IDやTouch IDをアプリ内でも使用してみたいと思い試してみましたのでご紹介したいと思います。

環境

この記事は以下のバージョン環境のもと作成されたものです。
【Xcode】14.2
【iOS】16.3
【macOS】Monterey

LocalAuthentication

Face IDやTouch IDはiPhoneで標準に備わっている機能で、大半の方がiPhoneの初期設定で行うと思います。(デバイスによりますが)
その時に登録した顔や指紋の情報はセキュリティを最大限に高めるために、OSとは分離されたハードウェアベースのセキュリティプロセッサであるSecure Enclaveで管理されます。
Secure Enclaveで管理されている生体情報を使用して認証を行うためにはLocalAuthenticationフレームワークを使用する必要があります。

https://developer.apple.com/documentation/localauthenticationから引用

以下ドキュメントリンクです。

developer.apple.com

またFace IDやTouch IDを使用してログインするサンプルコードもあります。
Apple Developer Documentation

サンプルコードを試してみた様子

一見生体認証機能の実装って難しそうに見えますが、ドキュメントやサンプルコードを見る限りシンプルで以下メソッド叩けばとりあえず実装できそうな感じです。

Task {
    do {
        try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Log in to your account")
        state = .loggedin
    } catch let error {
        print(error.localizedDescription)

        // Fall back to a asking for username and password.
        // ...
    }
}

試してみる

ここからは試しに実装してみたいと思います。
今回使用するデバイスはiPhone 14 Proとなりますので、Face IDを使用していきたいと思います。

Privacy - Face ID Usage Descriptionを追加

生体認証を追加したい場合はinfo.plistにPrivacy - Face ID Usage Descriptionを追加する必要があります。

ViewやClassの実装

まず画面を作成します。

struct BioAuthView: View {
    @State var flag = true
    @StateObject var faceAuth = FaceAuth()
    var body: some View {
        ZStack {
            Color.yellow
                .ignoresSafeArea()
            VStack {
                Spacer()
                Text(faceAuth.state == .loggedin ? "状態:ログイン" : "状態:ログアウト")
                    .foregroundColor(.black)
                Spacer()
                Button {
                    faceAuth.authStateChanger()
                } label: {
                    Text(faceAuth.state == .loggedin ? "ログアウト" : "認証開始")
                        .foregroundColor(.white)
                        .padding()
                        .padding(.horizontal, 60)
                        .background(
                            RoundedRectangle(cornerRadius: 16)
                                .foregroundColor(.blue)
                        )
                }
            }
        }
    }
}

samplerでは認証状態に関してEnum使っていたので同じようにします。

enum AuthenticationState {
    case loggedin
    case loggedout
}

最後にFace IDを使用する為のClassを作成します。
このClassでLocalAuthenticationフレームワークを使用するのでImportしてください。

import LocalAuthentication //必須です

final class FaceAuth: ObservableObject {
    @Published private(set) var state: AuthenticationState = .loggedout
    var context: LAContext = LAContext()
    let reason = "Face IDを使用する場合は設定より\nアクセスの許可を変更してください。"

    @MainActor
    func authStateChanger() {
        if state == .loggedin {
            state = .loggedout
        } else {
            context = LAContext()
            context.localizedCancelTitle = "パスワード入力で認証を行う"
            var error: NSError?
            guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
                print(error?.localizedDescription ?? "このデバイスでは生体認証を行うことができません")
                return
            }
            Task {
                do {
                    try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason)
                    state = .loggedin
                } catch {
                    print(error.localizedDescription)
                }
            }
        }
    }
}

以上で完成です!
起動するとFace IDを使用して良いか確認してきます。

許可をすることでFace IDを使用することができます。

解説

LAContextは認証ポリシーやアクセス制御を評価するためのもので、LAContext()とすることで初期化して認証を行なっています。
よってelse直下にあるcontext = LAContext()を行わないと、一度認証した後は認証したままとなりますので以下のような挙動になります。

context.localizedCancelTitleは生体認証に失敗した際に表示されるアラートの下部の文言を定義することができます。

localizedReasonでは生体認証の許可をしていない場合に表示される文言を定義することができます。

エラーハンドリングに関してはcatchの中で以下のように行うことができます。

guard let laError = error as? LAError else {
   return print("unknown")
}; switch laError.code {

case .authenticationFailed:
    print("authenticationFailed")
case .userCancel:
    print("userCancel")
case .userFallback:
    print("userFallback")
case .systemCancel:
    print("systemCancel")
case .passcodeNotSet:
    print("passcodeNotSet")
case .touchIDNotAvailable:
    print("touchIDNotAvailable")
case .touchIDNotEnrolled:
    print("touchIDNotEnrolled")
case .touchIDLockout:
    print("touchIDLockout")
case .appCancel:
    print("appCancel")
case .invalidContext:
    print("invalidContext")
case .notInteractive:
    print("notInteractive")
@unknown default:
    break
}

また生体認証方式の判定にはLABiometryTypeで取れるようです。

developer.apple.com

まとめ

生体認証と聞くとなんだか難しそうでしたが、非常に簡単に行う事ができました。こんなに簡単に実装できるなら次に個人開発を行う際はどこかで取り入れたいなと思いました!
以上生体認証を実装する方法でした。

執筆者岡優志

iOSエンジニア
iOSを専門とし、モバイルアプリの開発を行なっています。

Twitter