Touch to activate: Touch ID, Face ID and LocalAuthentication
Touch to activate: Touch ID, Face ID and LocalAuthentication êŽë š
Touch ID and Face ID are part of the Local Authentication framework, and our code needs to do three things:
- Check whether the device is capable of supporting biometric authentication â that the hardware is available and is configured by the user.
- 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.
- 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.
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) {
[weak self] success, authenticationError in
DispatchQueue.main.async {
if success {
self?.unlockSecretMessage()
} else {
// error
}
}
}
} else {
// no biometry
}
}
There is one important new piece of syntax in there that we havenât used before, which is &error
. The LocalAuthentication framework uses the Objective-C approach to reporting errors back to us, which is where the NSError
type comes from â where Swift likes to have an enum that conforms to the Error
protocol, Objective-C had a dedicated NSError
type for handling errors.
Here, though, we want LocalAuthentication to tell us what went wrong, and it canât do that by returning a value from the canEvaluatePolicy()
method â that already returns a Boolean telling us whether biometric authentication is available or not. So, instead what we use is the Objective-C equivalent of Swiftâs inout
parameters: we pass an empty NSError
variable into our call to canEvaluatePolicy()
, and if an error occurs that error will get filled with a real NSError
instance telling us what went wrong.
Objective-Câs equivalent to inout
is whatâs called a pointer, so named because it effectively points to a place in memory where something exists rather us passing around the actual value instead. If we had passed error
into the method, it would mean âhereâs the error you should use.â By passing in &error
â Objective-Câs equivalent of inout
â it means âif you hit an error, hereâs the place in memory where you should store that error so I can read it.â
I hope you can now see this is another example of why Swift was such a leap forward compared to Objective-C â having to pass around pointers to things wasnât terribly pleasant!
Apart from that, there are a couple of reminders: we need [weak self]
inside the first closure but not the second because it's already weak 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, so go ahead and try running the app now. If youâre using a physical device your regular Touch ID / Face ID should work just fine, but 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.