NEW: Join my free 100 Days of SwiftUI challenge today! >>

Using Touch ID and Face ID with SwiftUI

Paul Hudson    @twostraws   

The vast majority of Apple’s devices come with biometric authentication as standard, which means they use fingerprint and facial recognition to unlock. This functionality is available to us too, which means we can make sure that sensitive data can only be read when unlocked by a valid user.

This is another Objective-C API, but it’s only a little bit unpleasant to use with SwiftUI, which is better than we’ve had with some other frameworks we’ve looked at so far.

Before we write any code, you need to add a new key to your Info.plist file, explaining to the user why you want access to Face ID. For reasons known only to Apple, we pass the Touch ID request reason in code, and the Face ID request reason in Info.plist.

Open Info.plist now, right-click on some space, then choose Add Row. Scroll through the list of keys until you find “Privacy - Face ID Usage Description” and give it the value “We need to unlock your data.”

Now head back to ContentView.swift, and add this import near the top of the file:

import LocalAuthentication

OK, we’re all set to use biometrics. I mentioned earlier this was “only a little bit unpleasant”, and here’s where it comes in: Swift developers use the Error protocol for representing errors that occur at runtime, but Objective-C uses a special class called NSError. Because this is an Objective-C API we need to use NSError to handle problems, and pass it using & like a regular inout parameter.

We’re going to write an authenticate() method that isolates all the biometric functionality in a single place. To make that happen requires four steps:

  1. Create instance of LAContext, which allows us to query biometric status and perform the authentication check.
  2. Ask that context whether it’s capable of performing biometric authentication – this is important because iPod touch has neither Touch ID nor Face ID.
  3. If biometrics are possible, then we kick off the actual request for authentication, passing in a closure to run when authentication completes.
  4. When the user has either been authenticated or not, our completion closure will be called and tell us whether it worked or not, and if not what the error was. This closure will get called away from the main thread, so we need to push any UI-related work back to the main thread.

Please go ahead and add this method to ContentView:

func authenticate() {
    let context = LAContext()
    var error: NSError?

    // check whether biometric authentication is possible
    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
        // it's possible, so go ahead and use it
        let reason = "We need to unlock your data."

        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
            // authentication has now completed
            DispatchQueue.main.async {
                if success {
                    // authenticated successfully
                } else {
                    // there was a problem
                }
            }
        }
    } else {
        // no biometrics
    }
}

That method by itself won’t do anything, because it’s not connected to SwiftUI at all. To fix that we need to do add some state we can adjust when authentication is successful, and also an onAppear() modifier to trigger authentication.

So, first add this property to ContentView:

@State private var isUnlocked = false

That simple Boolean will store whether the app is showing its protected data or not, so we’ll flip that to true when authentication succeeds. Replace the // authenticated successfully comment with this:

self.isUnlocked = true

Finally, we can show the current authentication state and begin the authentication process inside the body property, like this:

VStack {
    if self.isUnlocked {
        Text("Unlocked")
    } else {
        Text("Locked")
    }
}
.onAppear(perform: authenticate)

If you run the app there’s a good chance you just see “Locked” and nothing else. This is because the simulator isn’t opted in to biometrics by default, and we didn’t provide any error messages, so it fails silently.

To take Face ID for a test drive, go to the Hardware menu and choose Face ID > Enrolled, then launch the app again. This time you should see the Face ID prompt appear, and you can trigger successful or failed authentication by going back to the Hardware menu and choosing Face ID > Matching Face or Non-matching Face.

All being well you should see the Face ID prompt go away, and underneath it will be the “Unlocked” text view – our app has detected the authentication, and is now open to use.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 3.5/5