How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID

Tim Mitra
Update note: This tutorial has been updated for Xcode 9.2, Swift 4, iOS 11 and the iPhone X by Tim Mitra. The original tutorial was also written by Tim Mitra.
How To Secure iOS User Data: The Keychain and Biometrics - Face ID or Touch ID

Learn how to secure your app using Face ID or Touch ID

Protecting an app with a login screen is a great way to secure user data – you can use the Keychain, which is built right in to iOS, to ensure their data stays secure. Apple also offers yet another layer of protection with Face ID and Touch ID. 

Available since the iPhone 5S, biometric data is stored in a secure enclave in the A7 and newer chips. All of this means you can comfortably hand over the responsibility of handling login information to the Keychain and either Face ID or Touch ID. 

In this tutorial you’ll start out with static authentication. Next you’ll be using the Keychain to store and verify login information. Finally, you’ll explore using Touch ID or Face ID in your app.

Note: Face ID requires you test on a physical device. Touch ID can now be emulated in Xcode 9 in the Simulator. The Keychain can also be used in the simulator. Throughout the tutorial I refer to Touch ID and it applies to Face ID in most cases. Under the hood, is the Local Authentication framework.

Getting Started

Download the starter project for this tutorial here.

This is a basic note taking app that uses Core Data to store user notes; the storyboard has a login view where users can enter a username and password, and the rest of the app’s views are already connected to each other and ready to use.

Build and run to see what your app looks like in its current state:

TouchMeIn starter

Note: You can ignore any compiler error about Note type missing. It will be autogenerated by Core Data

At this point, tapping the Login button simply dismisses the view and displays a list of notes – you can also create new notes from this screen. Tapping Logout takes you back to the login view. If the app is pushed to the background it will immediately return to the login view; this protects data from being viewed without being logged in.

Before you do anything else, you should change the Bundle Identifier, and assign an appropriate Team.

Select TouchMeIn in the Project navigator, and then select the TouchMeIn target. In the General tab change Bundle Identifier to use your own domain name, in reverse-domain-notation – for example com.raywenderich.TouchMeIn.

Then, from the Team menu, select the team associated with your developer account like so:

With all of the housekeeping done, it’s time to code! :]

Logging? No. Log In.

To get the ball rolling, you’re going to add the ability to check the user-provided credentials against hard-coded values.

Open LoginViewController.swift and add the following constants just below managedObjectContext:

let usernameKey = "Batman"
let passwordKey = "Hello Bruce!"

These are simply the hard-coded username and password you’ll check the user-provided credentials against.

Next, add the following method below loginAction(_:):

func checkLogin(username: String, password: String) -> Bool {
  return username == usernameKey && password == passwordKey
}

Here you check the user-provided credentials against the constants previously defined.

Next, replace the contents of loginAction(_:) with the following:

if checkLogin(username: usernameTextField.text!, password: passwordTextField.text!) {
  performSegue(withIdentifier: "dismissLogin", sender: self)
}

Here you call checkLogin(username:password:), which dismisses the login view only if the credentials are correct.

Build and run. Enter the username Batman and the password Hello Bruce!, and tap the Login button. The login screen should dismiss as expected.

While this simple approach to authentication seems to work, it’s not terribly secure, as credentials stored as strings can easily be compromised by curious hackers with the right tools and training. As a best practice, passwords should NEVER be stored directly in the app. To that end, you’ll employ the Keychain to store the password.

Note: Passwords in most apps are simply strings and are hidden as bullets. The best way to handle a password in your app is to SALT it and/or encrypt it with a SHA-2 encryption as soon as it is captured. Only the user should know the actual string. This is beyond the scope of this tutorial, but you should keep this in mind.

Check out Ryan Ackermann’s Basic iOS Security: Keychain and Hashing tutorial for the lowdown on how the Keychain works as well as password salting.

Rapper? No. Wrapper.

The next step is to add a Keychain wrapper to your app.

Along with the starter project, you downloaded a folder with useful resources. Locate and open the Resources folder in Finder. You’ll see the file KeychainPasswordItem.swift; this class comes from Apple’s sample code GenericKeychain.

Drag the KeychainPasswordItem.swift into the project, like so:

When prompted, make sure Copy items if needed and TouchMeIn target are both checked:

Copy files if needed

Build and run to make sure you have no errors. All good? Great — now you can leverage the Keychain from within your app.

Keychain, Meet Password. Password, Meet Keychain

To use the Keychain, you first store a username and password in it. Next, you’ll check the user-provided credentials against the Keychain to see if they match.

You’ll track whether the user has already created some credentials so you can change the text on the Login button from “Create” to “Login”. You’ll also store the username in the user defaults so you can perform this check without hitting the Keychain each time.

The Keychain requires some configuration to properly store your app’s information. You’ll provide that configuration in the form of a serviceName and an optional accessGroup. You’ll use a struct to store these values.

Open LoginViewController.swift. Add the following just below the import statements:

// Keychain Configuration
struct KeychainConfiguration {
  static let serviceName = "TouchMeIn"
  static let accessGroup: String? = nil
}

Next, add the following below managedObjectContext:

var passwordItems: [KeychainPasswordItem] = []
let createLoginButtonTag = 0
let loginButtonTag = 1

@IBOutlet weak var loginButton: UIButton!

passwordItems is an empty array of KeychainPasswordItem types you’ll pass into the keychain. You’ll use the next two constants to determine if the Login button is being used to create some credentials, or to log in; you’ll use the loginButton outlet to update the title of the button depending on its state.

Next, you’ll handle the two cases for when the button is tapped: if the user hasn’t yet created their credentials, the button text will show “Create”, otherwise the button will show “Login”.

First you’ll need a way to tell the user if the login fails. Add the following after checkLogin(username:password:):

private func showLoginFailedAlert() {
  let alertView = UIAlertController(title: "Login Problem",
                                    message: "Wrong username or password.",
                                    preferredStyle:. alert)
  let okAction = UIAlertAction(title: "Foiled Again!", style: .default)
  alertView.addAction(okAction)
  present(alertView, animated: true)
}

Now, replace loginAction(sender:) with the following:

@IBAction func loginAction(sender: UIButton) {
  // 1
  // Check that text has been entered into both the username and password fields.
  guard let newAccountName = usernameTextField.text,
    let newPassword = passwordTextField.text,
    !newAccountName.isEmpty,
    !newPassword.isEmpty else {
      showLoginFailedAlert()
      return
  }
    
  // 2
  usernameTextField.resignFirstResponder()
  passwordTextField.resignFirstResponder()
    
  // 3
  if sender.tag == createLoginButtonTag {
    // 4
    let hasLoginKey = UserDefaults.standard.bool(forKey: "hasLoginKey")
    if !hasLoginKey && usernameTextField.hasText {
      UserDefaults.standard.setValue(usernameTextField.text, forKey: "username")
    }
      
    // 5
    do {
      // This is a new account, create a new keychain item with the account name.
      let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
                                              account: newAccountName,
                                              accessGroup: KeychainConfiguration.accessGroup)
        
      // Save the password for the new item.
      try passwordItem.savePassword(newPassword)
    } catch {
      fatalError("Error updating keychain - \(error)")
    }
      
    // 6
    UserDefaults.standard.set(true, forKey: "hasLoginKey")
    loginButton.tag = loginButtonTag
    performSegue(withIdentifier: "dismissLogin", sender: self)
  } else if sender.tag == loginButtonTag {
     // 7
    if checkLogin(username: newAccountName, password: newPassword) {
      performSegue(withIdentifier: "dismissLogin", sender: self)
    } else {
      // 8
      showLoginFailedAlert()
    }
  }
}

Here’s what’s happening in the code:

  1. If either the username or password is empty, you present an alert to the user and return from the method.
  2. Dismiss the keyboard if it’s visible.
  3. If the login button’s tag is createLoginButtonTag, then proceed to create a new login.
  4. Next, you read hasLoginKey from UserDefaults which you use to indicate whether a password has been saved to the Keychain. If hasLoginKey is false and the username field has any text, then you save that text as username to UserDefaults.
  5. You create a KeychainPasswordItem with the serviceNamenewAccountName (username) and accessGroup. Using Swift’s error handling, you try to save the password. The catch is there if something goes wrong.
  6. You then set hasLoginKey in UserDefaults to true to indicate a password has been saved to the keychain. You set the login button’s tag to loginButtonTag to change the button’s text, so it will prompt the user to log in the next time they run your app, rather than prompting the user to create a login. Finally, you dismiss loginView.
  7. If the user is logging in (as indicated by loginButtonTag), you call checkLogin to verify the user-provided credentials; if they match then you dismiss the login view.
  8. If the login authentication fails, then present an alert message to the user.
Note: Why not just store the password along with the username in UserDefaults? That would be a bad idea because values stored in UserDefaults are persisted using a plist file. This is essentially an XML file that resides in the app’s Library folder, and is therefore readable by anyone with physical access to the device. The Keychain, on the other hand, uses the Triple Digital Encryption Standard (3DES) to encrypt its data. Even if somebody gets the data, they won’t be able to read it.

Next, replace checkLogin(username:password:) with the following updated implementation:

func checkLogin(username: String, password: String) -> Bool {
  guard username == UserDefaults.standard.value(forKey: "username") as? String else {
    return false
  }
    
  do {
    let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
                                            account: username,
                                            accessGroup: KeychainConfiguration.accessGroup)
    let keychainPassword = try passwordItem.readPassword()
    return password == keychainPassword
  } catch {
    fatalError("Error reading password from keychain - \(error)")
  }
}

Here you check that the username entered matches the one stored in UserDefaults and that the password matches the one stored in the Keychain.

Next, delete the following lines:

let usernameKey = "Batman"
let passwordKey = "Hello Bruce!"

Now it’s time to set the button title and tags appropriately depending on the state of hasLoginKey.

Add the following code to viewDidLoad(), just below the call to super:

// 1
let hasLogin = UserDefaults.standard.bool(forKey: "hasLoginKey")
    
// 2
if hasLogin {
  loginButton.setTitle("Login", for: .normal)
  loginButton.tag = loginButtonTag
  createInfoLabel.isHidden = true
} else {
  loginButton.setTitle("Create", for: .normal)
  loginButton.tag = createLoginButtonTag
  createInfoLabel.isHidden = false
}
    
// 3
if let storedUsername = UserDefaults.standard.value(forKey: "username") as? String {
  usernameTextField.text = storedUsername
}

Taking each numbered comment in turn:

  1. You first check hasLoginKey to see if you’ve already stored a login for this user.
  2. If so, change the button’s title to Login, update its tag to loginButtonTag, and hide createInfoLabel, which contains the informative text “Start by creating a username and password“. In case you don’t have a stored login for this user, you set the button label to Create and display createInfoLabel to the user.
  3. Finally, you set the username field to what is saved in UserDefaults to make logging in a little more convenient for the user.

Finally, you need to connect your outlet to the Login button. Open Main.storyboard and select the Login View Controller Scene. Ctrl-drag from the Login View Controller to the Login button, as shown below:

From the resulting popup, choose loginButton:

Build and run. Enter a username and password of your own choosing, then tap Create.

Note: If you forgot to connect the loginButton IBOutlet then you might see the error Fatal error: unexpectedly found nil while unwrapping an Optional value. If you do, connect the outlet as described in the relevant step above.

Now tap Logout and attempt to login with the same username and password – you should see the list of notes appear.

Tap Logout and try to log in again; this time, use a different password and then tap Login. You should see the following alert:

wrong password

Congratulations – you’ve now added authentication use the Keychain. Next up, Touch ID.

Touching You, Touching Me

Note: Face ID requires that you test on a physical device, such as an iPhone X. Touch ID can now be emulated in Xcode 9 in the Simulator. You can test biometric ID on any device with a A7 chip or newer and Face ID/Touch ID hardware.

In this section, you’ll add biometric ID to your project in addition to using the Keychain. While Keychain isn’t necessary for Face ID/Touch ID to work, it’s always a good idea to implement a backup authentication method for instances where biometric ID fails, or for users that don’t have at least a Touch ID compatible device.

Open Assets.xcassets.

Next, open the Resources folder from the starter project you downloaded earlier in Finder. Locate FaceIcon and Touch-icon-lg.png images (all three sizes), select all six and drag them into Images.xcassets so that Xcode knows they’re the same image, only with different resolutions:

Open Main.storyboard and drag a Button from the Object Library onto the Login View Controller Scene, just below the Create Info Label, inside the Stack View. You can open the Document Outline, swing open the disclosure triangles and make sure that the Button is inside the Stack View. It should look like this:

If you need to review stack views, take a look at UIStackView Tutorial: Introducing Stack Views.

In the Attributes inspector, adjust the button’s attributes as follows:

  • Set Type to Custom.
  • Leave the Title empty.
  • Set Image to Touch-icon-lg.

When you’re done, the button’s attributes should look like this:

Screen Shot 2014-12-22 at 3.05.58 AM

Ensure your new button is selected, then click the Add New Constraints button in the layout bar at the foot of the storyboard canvas and set the constraints as below:

constraints on Touch button

  • Width should be 66
  • Height should be 67

Click Add 2 Constrains. Your view should now look like the following:

login view

Still in Main.storyboard, open the Assistant Editor and make sure LoginViewController.swift is showing.

Next, Control-drag from the button you just added to LoginViewController.swift, just below the other IBOutlets, like so:

connect touchButton

In the popup enter touchIDButton as the Name and click Connect:

This creates an IBOutlet you’ll use to hide the button on devices that don’t have biometric ID available.

Next, you need to add an action for the button.

Control-drag from the same button to LoginViewController.swift to just above checkLogin(username:password:):

connect touch action

In the popup, change Connection to Action, set Name to touchIDLoginAction, set Arguments to none for now. Then click Connect.

Build and run to check for any errors. You can still build for the Simulator at this point since you haven’t yet added support for biometric ID. You’ll take care of that now.

Adding Local Authentication

Implementing biometric ID is as simple as importing the Local Authentication framework and calling a couple of simple yet powerful methods.

Here’s what the Local Authentication documentation has to say:

“The Local Authentication framework provides facilities for requesting authentication from users with specified security policies.”

The specified security policy in this case will be your user’s biometrics — A.K.A their face or fingerprint! :]

New in iOS 11 is support for Face ID. LocalAuthentication adds a couple of new things: a required FaceIDUsageDescription and a LABiometryType to determine whether the device supports Face ID or Touch ID.

In Xcode’s Project navigator select the project and click the Info tab. Hover over the right edge of one of the Keys and click the +. Start typing “Privacy” and from the pop up list that appears select “Privacy – Face ID Usage Description” as the key.

Note: you can also enter “NSFaceIDUsageDescription” as the key.

The type should be a String. In the value field enter We use Face ID to unlock the notes.

In the Project navigator right-click the TouchMeIn group folder and select New File…. Choose iOS\Swift File. Click Next. Save the file as TouchIDAuthentication.swift with the TouchMeIn target checked. Click Create.

Open TouchIDAuthentication.swift and add the following import just below import Foundation:

import LocalAuthentication

Next, add the following to create a new class:

class BiometricIDAuth {
  
}

Now you’ll need a reference to the LAContext class.

Inside the class add the following code between the curly braces:

let context = LAContext()

The context references an authentication context, which is the main player in Local Authentication. You’ll need a function to see if biometric ID is available to the user’s device or in the Simulator.

Add the following method to return a Bool if biometric ID is supported inside BiometricIDAuth:

func canEvaluatePolicy() -> Bool {
  return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}

Open LoginViewController.swift and add the following property to create a reference to BiometricIDAuth.

let touchMe = BiometricIDAuth()

At the bottom of viewDidLoad() add the following:

touchIDButton.isHidden = !touchMe.canEvaluatePolicy()

Here you use canEvaluatePolicy(_:) to check whether the device can implement biometric authentication. If so, show the Touch ID button; if not, leave it hidden.

Build and run on the Simulator; you’ll see the Touch ID logo is hidden. Now build and run on your physical Face ID/Touch ID-capable device; you’ll see the Touch ID button is displayed. In the Simulator you can choose Touch ID > Enrolled from the Hardware menu and test the button.

Face ID or Touch ID

If you’re running on an iPhone X or later Face ID equipped device you may notice a problem. You’ve taken care of the Face ID Usage Description, and now the Touch ID icon seems out of place. You’ll use the biometryType enum to fix this.

Open, TouchIDAuthentication.swift and add a BiometricType enum above the class.

enum BiometricType {
  case none
  case touchID
  case faceID
}

Next, add the following function to return which biometric type is supported using the canEvaluatePolicy.

func biometricType() -> BiometricType {
  let _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
  switch context.biometryType {
  case .none:
    return .none
  case .touchID:
    return .touchID
  case .faceID:
    return .faceID
  }
}

Open, LoginViewController and add the following to the bottom of viewDidLoad() to fix the button’s icon.

switch touchMe.biometricType() {
case .faceID:
  touchIDButton.setImage(UIImage(named: "FaceIcon"),  for: .normal)
default:
  touchIDButton.setImage(UIImage(named: "Touch-icon-lg"),  for: .normal)
}

Build and run on the Simulator with Touch ID Enrolled to see the Touch ID icon; you’ll see the correct icon is shown on the iPhone X – the Face ID icon.

Putting Touch ID to Work

Open, TouchIDAuthentication.swift and add the following variable below context:

var loginReason = "Logging in with Touch ID"

The above provides the reason the application is requesting authentication. It will display to the user on the presented dialog.

Next, add the following method to the bottom of BiometricIDAuth to authenticate the user:

func authenticateUser(completion: @escaping () -> Void) { // 1
  // 2
  guard canEvaluatePolicy() else {
    return
  }

  // 3
  context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
    localizedReason: loginReason) { (success, evaluateError) in
      // 4
      if success {
        DispatchQueue.main.async {
          // User authenticated successfully, take appropriate action
          completion()
        }
      } else {
        // TODO: deal with LAError cases
      }
  }
}

Here’s what’s going on in the code above:

  1. authenticateUser(completion:) takes a completion handler in the form of a closure.
  2. You’re using canEvaluatePolicy() to check whether the device is capable of biometric authentication.
  3. If the device does support biometric ID, you then use evaluatePolicy(_:localizedReason:reply:) to begin the policy evaluation — that is, prompt the user for biometric ID authentication. evaluatePolicy(_:localizedReason:reply:) takes a reply block that is executed after the evaluation completes.
  4. Inside the reply block, you are handling the success case first. By default, the policy evaluation happens on a private thread, so your code jumps back to the main thread so it can update the UI. If the authentication was successful, you will call the segue that dismisses the login view.

You’ll come back and deal with errors in little while.

Open, LoginViewController.swift, locate touchIDLoginAction(_:) and replace it with the following:

@IBAction func touchIDLoginAction() {    
  touchMe.authenticateUser() { [weak self] in
    self?.performSegue(withIdentifier: "dismissLogin", sender: self)
  }
}

If the user is authenticated, you can dismiss the Login view.

Go ahead and build and run to see if all’s well.

Dealing with Errors

Wait! What if you haven’t set up biometric ID on your device? What if you are using the wrong finger or are wearing a disguise? Let’s deal with that.

An important part of Local Authentication is responding to errors, so the framework includes an LAError type. There also is the possibility of getting an error from the second use of canEvaluatePolicy.

You’ll present an alert to show the user what has gone wrong. You will need to pass a message from the TouchIDAuth class to the LoginViewController. Fortunately you have the completion handler that you can use it to pass the optional message.

Open, TouchIDAuthentication.swift and update the authenticateUser method.

Change the signature to include an optional message you’ll pass when you get an error.

func authenticateUser(completion: @escaping (String?) -> Void) {

Next, find the // TODO: and replace it with the following:

// 1
let message: String
            
// 2
switch evaluateError {
// 3
case LAError.authenticationFailed?:
  message = "There was a problem verifying your identity."
case LAError.userCancel?:
  message = "You pressed cancel."
case LAError.userFallback?:
  message = "You pressed password."
case LAError.biometryNotAvailable?:
  message = "Face ID/Touch ID is not available."
case LAError.biometryNotEnrolled?:
  message = "Face ID/Touch ID is not set up."
case LAError.biometryLockout?:
  message = "Face ID/Touch ID is locked."
default:
  message = "Face ID/Touch ID may not be configured"
}
// 4
completion(message)

Here’s what’s happening

  1. Declare a string to hold the message.
  2. Now for the “failure” cases. You use a switch statement to set appropriate error messages for each error case, then present the user with an alert view.
  3. If the authentication failed, you display a generic alert. In practice, you should really evaluate and address the specific error code returned, which could include any of the following:
    • LAError.biometryNotAvailable: the device isn’t Face ID/Touch ID-compatible.
    • LAError.passcodeNotSet: there is no passcode enabled as required for Touch ID
    • LAError.biometryNotEnrolled: there are no face or fingerprints stored.
    • LAError.biometryLockout: there were too many failed attempts.
  4. Pass the message in the completion closure.

iOS responds to LAError.passcodeNotSet and LAError.biometryNotEnrolled on its own with relevant alerts.

There’s one more error case to deal with. Add the following inside the else block of the guard statement, just above return.

completion("Touch ID not available")

The last thing to update is the success case. That completion should contain nil, indicating that you didn’t get any errors. Inside the first success block add the nil.

completion(nil)

Once you’ve completed these changes your finished method should look like this:

func authenticateUser(completion: @escaping (String?) -> Void) {
    
  guard canEvaluatePolicy() else {
    completion("Touch ID not available")
    return
  }
    
  context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
    localizedReason: loginReason) { (success, evaluateError) in
      if success {
        DispatchQueue.main.async {
          completion(nil)
        }
      } else {
                              
        let message: String
                              
        switch evaluateError {
        case LAError.authenticationFailed?:
          message = "There was a problem verifying your identity."
        case LAError.userCancel?:
          message = "You pressed cancel."
        case LAError.userFallback?:
          message = "You pressed password."
        case LAError.biometryNotAvailable?:
          message = "Face ID/Touch ID is not available."
        case LAError.biometryNotEnrolled?:
          message = "Face ID/Touch ID is not set up."
        case LAError.biometryLockout?:
          message = "Face ID/Touch ID is locked."
        default:
          message = "Face ID/Touch ID may not be configured"
        }
          
        completion(message)
      }
  }
}
Note: When you compile this new error handling, you will see three warnings complaining of using deprecated constants. This is due to a combination of the way Apple added support for Face ID and the way Swift imports Objective-C header files. There are some potential workarounds, but they are much less “Swift-like”. Since Apple is aware of the issue and plans to fix it at a future date, the cleaner code is presented here.

Open LoginViewController.swift and update the touchIDLoginAction(_:) to look like this:

@IBAction func touchIDLoginAction() {
  // 1
  touchMe.authenticateUser() { [weak self] message in
    // 2
    if let message = message {
      // if the completion is not nil show an alert
      let alertView = UIAlertController(title: "Error",
                                        message: message,
                                        preferredStyle: .alert)
      let okAction = UIAlertAction(title: "Darn!", style: .default)
      alertView.addAction(okAction)
      self?.present(alertView, animated: true)
    } else {
      // 3
      self?.performSegue(withIdentifier: "dismissLogin", sender: self)
    }
  }
}

Here’s what you’re doing in this code snippet:

  1. You’ve updated the trailing closure to accept an optional message. If biometric ID works, there is no message.
  2. You use if let to unwrap the message and display it with an alert.
  3. No change here, but if you have no message, you can dismiss the Login view.

Build and run on a physical device and test logging in with Touch ID.

Since LAContext handles most of the heavy lifting, it turned out to be relatively straight forward to implement biometric ID. As a bonus, you were able to have Keychain and biometric ID authentication in the same app, to handle the event that your user doesn’t have a Touch ID-enabled device.

Note: If you want test the errors in Touch ID, you can try to login with an incorrect finger. Doing so five times will disable Touch ID and require password authentication. This prevents strangers from trying to break into other applications on your device. You can re-enable it by going to Settings -> Touch ID & Passcode.

Look Mom! No Hands.

One of the coolest things about the iPhone X is using Face ID without touching the screen. You added a button which you can use to trigger the Face ID, but you can trigger Face ID automagically as well.

Open LoginViewController.swift and add the following code right below viewDidLoad():

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  let touchBool = touchMe.canEvaluatePolicy()
  if touchBool {
   touchIDLoginAction()
  }
}

The above will verify if biometric ID is supported and if so try and authenticate the user.

Build and run on an iPhone X or Face ID equipped device and test logging in hands free!

Where to Go from Here?

You can download the completed sample application from this tutorial here.

The LoginViewController you’ve created in this tutorial provides a jumping-off point for any app that needs to manage user credentials.

You can also add a new view controller, or modify the existing LoginViewController, to allow the user to change the password from time to time. This isn’t necessary with biometric ID, since the user’s biometrics probably won’t change much in their lifetime! :] However, you could create a way to update the Keychain; you’d want to prompt the user for their current password before accepting their modification.

Apple also recommends hiding the username and password fields and login button when using Face ID. I’ll leave that for you as a simple challenge.

You can read more about securing your iOS apps in Apple’s official iOS Security Guide.

As always, if you have any questions or comments on this tutorial, feel free to join the discussion below!

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Tim Mitra

Tim is a mobile app developer, podcaster and artist. He also teaches iOS dev, Swift and Objective-C. Tim is a lead iOS developer at TD Bank. He also runs iT Guy Technologies, a software development company in Toronto, Canada. He studied Fine Arts before there were Macs and he's worked for many years in software development, IT, graphic design, publishing and printing. He is also the founder, producer and host of the More Than Just Code Podcast on mobile app development & business. Tim is also co-host of Roundabout Creative Chaos podcast.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 27 total!

iOS Team

... 83 total!

Android Team

... 43 total!

Unity Team

... 16 total!

Articles Team

... 4 total!

Resident Authors Team

... 31 total!

Podcast Team

... 8 total!

Recruitment Team

... 8 total!

Illustration Team

... 4 total!