NEW! Master Swift design patterns with my latest book! >>

< Previous: Writing somewhere safe: the iOS keychain   Next: Wrap up >

Touch to activate: Touch ID, Face ID and LocalAuthentication

This part of the project requires a biometry-capable device, although I'll be showing you a hack to make it work on the simulator by bypassing checks at two points.

Touch ID and Face ID are part of the Local Authentication framework, and our code needs to three things:

  1. Check whether the device is capable of supporting biometric authentication.
  2. If so, request that the biometry system begin a check now, giving it a string containing the reason why we're asking. For Touch ID the string is written in code; for Face ID the string is written into our Info.plist file.
  3. If we get success back from the authentication request it means this is the device's owner so we can unlock the app, otherwise we show a failure message.

One caveat that you must be careful of: when we're told whether Touch ID/Face ID was successful or not, it might not be on the main thread. This means we need to use async() to make sure we execute any user interface code on the main thread.

Note: Xcode 9 shipped with Face ID quite broken; please use Xcode 9.1 or later to follow along.

The job of task 1 is done by the canEvaluatePolicy() method of the LAContext class, requesting the security policy type .deviceOwnerAuthenticationWithBiometrics. The job of task 2 is done by the evaluatePolicy() of that same class, using the same policy type, but it accepts a trailing closure telling us the result of the policy evaluation: was it successful, and if not what was the reason?

Like I said, all this is provided by the Local Authentication framework, so the first thing we need to do is import that framework. Add this above import UIKit:

import LocalAuthentication

And now here's the new code for the authenticateTapped() method. We already walked through what it does, so this shouldn't be too surprising:

@IBAction func authenticateTapped(_ sender: Any) {
    let context = LAContext()
    var error: NSError?

    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
        let reason = "Identify yourself!"

        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {
            [unowned self] (success, authenticationError) in

            DispatchQueue.main.async {
                if success {
                    self.unlockSecretMessage()
                } else {
                    // error
                }
            }
        }
    } else {
        // no biometry
    }
}

A couple of reminders: we need [unowned self] inside the first closure but not the second because it's already unowned by that point. You also need to use self. inside the closure to make capturing clear. Finally, you must provide a reason why you want Touch ID/Face ID to be used, so you might want to replace mine ("Identify yourself!") with something a little more descriptive.

You can see the “Identify yourself!” string in our code, which will be shown to Touch ID users. Face ID is handled slightly differently – open Info.plist, then add a new key called “Privacy - Face ID Usage Description”. This should contain similar text to what you use with Touch ID, so give it the value “Identify yourself!”.

That's enough to get basic biometric authentication working, but there are error cases you need to catch. For example, you’ll hit problems if the device does not have biometric capability or it isn’t configured. Similarly, you’ll get an error if the user failed authentication, which might be because their fingerprint or face wasn't scanning for whatever reason, but also if the system has to cancel scanning for some reason.

To catch authentication failure errors, replace the // error comment with this:

let ac = UIAlertController(title: "Authentication failed", message: "You could not be verified; please try again.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)

We also need to show an error if biometry just isn't available, so replace the // no Touch ID comment with this:

let ac = UIAlertController(title: "Biometry unavailable", message: "Your device is not configured for biometric authentication.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)

That completes the authentication code, but before we're done I want to add one more thing before the project is complete: if you don't have a biometry-capable device and you want to make sure the code works OK, you have a few options.

First, the hack: you can effectively disable biometry by changing these two lines:

if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {

if success {

…to read this both times:

if true {

That condition will always evaluate to true, so it will allow you to test the app using older devices.

Alternatively, if you’re using the Simulator there are useful options under the Hardware menu – go to Hardware > Touch ID/Face ID > Toggle Enrolled State to opt in to biometric authentication, then use Hardware > Touch ID/Face ID > Matching Touch/Face when you’re asked for a fingerprint/face.

Get six books for only $150

The Swift Power Pack includes my first six books for one low price, helping you jumpstart a new career in iOS development – check it out!

< Previous: Writing somewhere safe: the iOS keychain   Next: Wrap up >
MASTER SWIFT NOW
Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Practical iOS 11 Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>