TouchID authentication tutorial for Swift

TouchID

A few years ago Apple introduced TouchID on the iPhone 5S. Instead of asking your user for a password, you can just ask for their fingerprint (if their device has TouchID) which improves the UX by a gazillion times.

With the introduction of iOS7, it was impossible for a developer to use the fingerprint sensor for authentication. Luckily in iOS8, Apple provided us with an API to do so.

In this tutorial I’ll show you how you can integrate TouchID authentication in your application.

Local Authentication framework

If you want to work with TouchID, you’ll have to link your app with the Local Authentication framework.

Go to Project Settings > General and scroll down to the Linked Frameworks and Libraries section. Click on the + sign and add LocalAuthentication.framework.

Link the LocalAuthentication.framework with your app
Link the LocalAuthentication.framework with your app

Authentication view controller

For this example we’ll just show the user a screen with a login button. If the user touched the login button, he will be asked to authenticate via TouchID. If the authentication fails, we’ll show an error message. If it succeeds we’ll push a new view controller onto the navigation stack.

My Storyboard looks like this hierarchical:

  • UINavigationController: root view controller
    • AuthenticationViewController
      • UIButton : login
    • ProtectedContentViewController
      • UILabel : success message

I’ve added an IBAction in my AuthenticationViewController, and hooked it up to the UIButton.

First you’ll need to import the Local Authentication framework at the top of the AuthenticationViewController.

import LocalAuthentication
Swift

The Local Authentication framework is quite small for Apple standards. It only has one class named LAContext.

First we need to create an authentication context.  You do this by creating an instance of the LAContext class.

@IBAction func loginButtonClicked(sender: UIButton) {
        
   // 1. Create a authentication context
   let authenticationContext = LAContext()

   ...

}
Swift

Because not every device has a TouchID sensor, we need to ask the context if there is a fingerprint sensor.

Check for TouchID availability

You do this by calling the method canEvaluatePolicy:error. This method will return a boolean which you can use to provide a fallback authentication method if there is no TouchID available.

var error:NSError?
        
// 2. Check if the device has a fingerprint sensor
// If not, show the user an alert view and bail out!
guard authenticationContext.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: &error) else {
            
   showAlertViewIfNoBiometricSensorHasBeenDetected()
   return
            
}
Swift

I use the guard statement here introduced in Swift 2, because this method needs to return true, otherwise there is no point of executing the rest of the method.  For now, I’ll just present the user with an UIAlertViewController to give some feedback.

func showAlertViewIfNoBiometricSensorHasBeenDetected(){
        
   showAlertWithTitle("Error", message: "This device does not have a TouchID sensor.")
        
}

func showAlertWithTitle( title:String, message:String ) {
 
    let alertVC = UIAlertController(title: title, message: message, preferredStyle: .Alert)
    
    let okAction = UIAlertAction(title: "Ok", style: .Default, handler: nil)
    alertVC.addAction(okAction)
    
    dispatch_async(dispatch_get_main_queue()) { () -> Void in
    
        self.presentViewController(alertVC, animated: true, completion: nil)
        
    }
    
}
Swift

Validating the fingerprint

If the device does have a TouchID sensor, we want to present the user the typical TouchID form.

TouchID form
TouchID form

This popup is shown when you call the evaluatePolicy:localizedReason:reply: method on your authentication context.

This method has 3 arguments:

  1. policy: the policy to evaluate
  2. localizedReason: a string message that will be shown to the user, explaining why they need to provide their fingerprint
  3. reply: a closure which is executed when the evaluation ends. This closure has 2 arguments
    1. success: true if fingerprint is recognized, false if not
    2. error: a NSError object with an error code (check LAError for all options)

This is my implementation.

// 3. Check the fingerprint
authenticationContext.evaluatePolicy(
    .DeviceOwnerAuthenticationWithBiometrics,
    localizedReason: "Only awesome people are allowed",
    reply: { [unowned self] (success, error) -> Void in
        
    if( success ) {
        
        // Fingerprint recognized
        // Go to view controller
        self.navigateToAuthenticatedViewController()
        
    }else {
        
        // Check if there is an error
        if let error = error {
            
            let message = self.errorMessageForLAErrorCode(error.code)
            self.showAlertViewAfterEvaluatingPolicyWithMessage(message)
            
        }
        
    }
    
})
Swift

Handling success

The method navigateToAuthenticatedViewController just gets a view controller in my Storyboard and performs a pushViewController on the navigation controller.

func navigateToAuthenticatedViewController(){
    
    if let loggedInVC = storyboard?.instantiateViewControllerWithIdentifier("LoggedInViewController") {
        
        dispatch_async(dispatch_get_main_queue()) { () -> Void in
            
            navigationController?.pushViewController(loggedInVC, animated: true)
            
        }
        
    }
    
}
Swift

Handling failure

If there is an error, I have a method which returns an error message based on the error code.  The error codes can be compared to the LAError class.

func errorMessageForLAErrorCode( errorCode:Int ) -> String{
    
    var message = ""
    
    switch errorCode {
        
    case LAError.AppCancel.rawValue:
        message = "Authentication was cancelled by application"
        
    case LAError.AuthenticationFailed.rawValue:
        message = "The user failed to provide valid credentials"
        
    case LAError.InvalidContext.rawValue:
        message = "The context is invalid"
        
    case LAError.PasscodeNotSet.rawValue:
        message = "Passcode is not set on the device"
        
    case LAError.SystemCancel.rawValue:
        message = "Authentication was cancelled by the system"
        
    case LAError.TouchIDLockout.rawValue:
        message = "Too many failed attempts."
        
    case LAError.TouchIDNotAvailable.rawValue:
        message = "TouchID is not available on the device"
        
    case LAError.UserCancel.rawValue:
        message = "The user did cancel"
        
    case LAError.UserFallback.rawValue:
        message = "The user chose to use the fallback"
        
    default:
        message = "Did not find error code on LAError object"
        
    }
    
    return message
    
}
Swift

And that’s it.

Source code

The source code for this project can be found on GitHub.

Share this post

2 Responses

    1. Stored fingerprints in the Settings. So, really, the passcode for the device – as that is required for storing fingerprints. So, not really the app password

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts