Tag Archives: ios

Refactoring: Face ID/Touch ID for iOS 13 Update

Back in February 2015, my article on Touch ID was published on raywenderlich.com. I was written in Swift for Xcode 8. Every year or so i would update the article as an author on the iOS Team. Here’s a link to the latest — How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID. A few months ago, I had to update one of my own apps for iOS 13 with Apple’s biometric identification framework, Local Authentication. My app was also still supporting Objective-C so here’s follow up on what I had to change. As a bonus you can also take your user to Settings in case they have disabled

First thing is to add Local Authentication at the top of the Login view controller.

#import <LocalAuthentication/LocalAuthentication.h>

Next create an action for the Touch ID method:

- (IBAction)touchIDAction:(id)sender {
  LAContext *myContext = [[LAContext alloc] init];
  NSError *authError = nil;
  NSString *myLocalizedReasonString = @"Used for quick and secure access to the test app";
  //...
}

After that we need to check if the device can support biometrics with canEvaluatePolicy and have an error ready.

Inside the touchIDAction add:

if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics 
error:&authError]) {
  // 1. successful steps
} else {
  // 2. Oops. There's a error!
}

Inside the canEvaluatePolicy, we’ll use evaluatePolicy:localizedReason:reply. The reply will have a block that either succeeds or fails with our error.

// 1. successful steps.
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
   if (success) {
      dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
 //Background Thread
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
// using a Keychain utility method to get the email and password
NSString *passwordFound = [KeychainUtils getPasswordForUsername:self->emailTextField.text andServiceName:@"My_app" error:nil];
self->passwordTextField.text = passwordFound;
self->usingSecureID = true; // a Bool I added to keep track
 [self loginAction:nil];
  [NSLog showWithStatus:@"Logging_In"];
  });
});
} else {
    // User did not authenticate successfully, look at error and take appropriate action 
   //I'm using a showAlert method to bring up a UIAlertViewController
   [self showAlert: @"There was a problem verifying your identity." withTitle:@"Error!"];
    return;
 }
}];

What do we do if there is an error enabling Face ID/Touch ID? It could be because the user has disabled the feature. What’s new is that we can now take the user to your application settings — without a hack.

Initially you can pop up an alert to inform the user. Added to UIKit in iOS 8, UIApplicationOpenSettingsURLString lets you add a button to the alert that will take the user to your app in Settings, where they can enable Face ID/Touch ID.

// Could not evaluate policy; look at authError and present an appropriate message to user
    NSString *title = @"Error!";
    NSString *message = @"Your device cannot authenticate using TouchID.";
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault
                                                          handler:^(UIAlertAction * action) {
  // do we need to return animation?
                                                          }];
    // open your app in Settings
    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    UIApplication *application = [UIApplication sharedApplication];
    NSString *settingTitle = @"Settings";
    UIAlertAction* settingsAction = [UIAlertAction actionWithTitle:settingTitle style:UIAlertActionStyleDefault
                                                           handler:^(UIAlertAction * action) {
                                                             [application openURL:url  options:@{}
completionHandler:nil];
                                                           }];
    [alert addAction:settingsAction];
    [alert addAction:defaultAction];
    [self presentViewController:alert animated:YES completion:nil];
    return;
 }

The whole method would look like this:

- (IBAction)touchIDAction:(id)sender {
  LAContext *myContext = [[LAContext alloc] init];
  NSError *authError = nil;
  NSString *myLocalizedReasonString = @"Used for quick and secure access to the test app";
  if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
// 1. successful steps
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
   if (success) {
      dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
 //Background Thread
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
// using a Keychain utility method to get the email and password
NSString *passwordFound = [KeychainUtils getPasswordForUsername:self->emailTextField.text andServiceName:@"My_app" error:nil];
self->passwordTextField.text = passwordFound;
self->usingSecureID = true; // a Bool I added to keep track
 [self loginAction:nil];
  [NSLog showWithStatus:@"Logging_In"];
  });
});
} else {
    // User did not authenticate successfully, look at error and take appropriate action 
   //I'm using a showAlert method to bring up a UIAlertViewController
   [self showAlert: @"There was a problem verifying your identity." withTitle:@"Error!"];
    return;
 }
}];
} else {
// 2. Oops. There's a error!
// Could not evaluate policy; look at authError and present an appropriate message to user
    NSString *title = @"Error!";
    NSString *message = @"Your device cannot authenticate using TouchID.";
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault
                                                          handler:^(UIAlertAction * action) {
  // do we need to return animation?
                                                          }];
    // open your app in Settings
    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    UIApplication *application = [UIApplication sharedApplication];
    NSString *settingTitle = @"Settings";
    UIAlertAction* settingsAction = [UIAlertAction actionWithTitle:settingTitle style:UIAlertActionStyleDefault
                                                           handler:^(UIAlertAction * action) {
                                                             [application openURL:url  options:@{}
completionHandler:nil];
                                                           }];
    [alert addAction:settingsAction];
    [alert addAction:defaultAction];
    [self presentViewController:alert animated:YES completion:nil];
    return;
 }
}
}