Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4402

How To Secure iOS User Data: The Keychain and Touch ID

$
0
0
Learn how to add Touch ID & 1Password to your app!

Learn how to secure your app using Touch ID

Update note: This tutorial has been updated for Xcode 8.3.2 and Swift 3.1 by Tim Mitra. The original tutorial was also written by Tim Mitra.

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 that their data stays secure.  Apple also offers yet another layer of protection with Touch ID. Available since the iPhone 5s, Touch ID stores biometrics 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/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. After that, you’ll explore using Touch ID in your app.

Note: Touch ID requires that you test on a physical device, but the Keychain can be used in the simulator.

Getting Started

Please 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

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. This is controlled by setting Application does not run in background to YES in Info.plist.

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 where the managedObjectContext variable is declared:

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

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

Add the following function below loginAction(_:):

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

This checks the user-provided credentials against the constants you defined earlier.

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

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

This calls checkLogin(username:password:), which dismisses the login view 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. Check out Chris Lowe’s Basic Security in iOS 5 – Part 1 tutorial for the lowdown on how the Keychain works.

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

Rapper? No. Wrapper.

In the starter app you’ll find that you have already downloaded the KeychainPasswordItem.swift file; this class comes from Apple’s sample code GenericKeychain.

In the Resources folder, drag the KeychainPasswordItem.swift into the project, like so:

When prompted, make sure that Copy items if needed is checked and the TouchMeIn target is checked as well:

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 need to store a username and password. After that, you check the user-provided credentials against the Keychain to see if they match.

You’ll need to track whether the user has already created some credentials so that 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 will need to add a serviceName and an optional accessGroup. You’ll add a struct to store these values.

Open up LoginViewController.swift. At the top of the file and just below the imports add this struct.

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

Next delete the following lines:

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

In their place, add the following:

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

@IBOutlet weak var loginButton: UIButton!

The passwordItems is an empty array of KeychainPasswordItem types that will pass into the keychain. The next two constants will be used to determine if the Login button is being used to create some credentials, or to log in; the loginButton outlet will be used to update the title of the button depending on that same state.

Open Main.storyboard and choose 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:

Next, you need to handle the following 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”. You also need to check the entered credentials against the Keychain.

Open LoginViewController.swift and replace the code in loginAction(_:) with the following:

  @IBAction func loginAction(_ sender: AnyObject) {
    // 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 {

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

    // 2
    usernameTextField.resignFirstResponder()
    passwordTextField.resignFirstResponder()

    // 3
    if sender.tag == createLoginButtonTag {

      // 4
      let hasLoginKey = UserDefaults.standard.bool(forKey: "hasLoginKey")
      if !hasLoginKey {
        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: usernameTextField.text!, password: passwordTextField.text!) {
        performSegue(withIdentifier: "dismissLogin", sender: self)
      } else {
        // 8
        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, completion: nil)
      }
    }
  }

Here’s what’s happening in the code:

  1. If either the username or password is empty, then 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 indicates whether a password has been saved to the Keychain. If the username field is not empty and hasLoginKey indicates no login has already been saved, then you save the username to UserDefaults.
  5. You create a KeychainPasswordItem with the serviceNamenewAccountName (username) and accessGroup. Using Swift’s error handling, you try to save save the password. The catch is there if something goes wrong.
  6. You then set hasLoginKey in UserDefaults to true to indicate that a password has been saved to the keychain. You set the login button’s tag to loginButtonTag to change the button’s text, so that 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 the implementation of checkLogin(username:password:) with the following:

  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)")
    }

    return false
  }

This checks that the username entered matches the one stored in UserDefaults and that the password matches the one stored in the Keychain.

Now you need to set the button title and tags appropriately depending on the state of hasLoginKey.

Add the following 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“. If you don’t have a stored login for this user, 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.

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: In order to test Touch ID, you’ll need to run the app on a physical device that supports Touch ID. At the time of this writing, that includes any device with a A7 chip or newer and Touch ID hardware.

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

Open Images.xcassets.

Open the Resources folder from the starter project you downloaded earlier. Locate Touch-icon-lg.png, Touch-icon-lg@2x.png, and Touch-icon-lg@3x.png, select all three and drag them into Images.xcassets so that Xcode knows they’re the same image, only with different resolutions:

drag to assets

Open up 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 Jawwad Ahmad’s UIStackView Tutorial: Introducing Stack Views.

Use the Attributes Inspector to 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

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.

Now, Ctrl-drag from the button you just added to LoginViewController.swift, just below the other properties, like so:

connect touchButton

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

naming outlet

This creates an outlet that you will use to hide the button on devices that don’t have Touch ID available.

Now you need to add an action for the button.

Ctrl-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, optionally set the Type to UIButton. Then click Connect.

touchIDLoginAction

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 Touch ID. You’ll take care of that now.

Adding Local Authentication

Implementing Touch 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 fingerprint! :]

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

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

import LocalAuthentication

Create a new class next:

class TouchIDAuth {

}

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 will need a function to see if Touch ID is available to the user’s device or in the Simulator.

Create the following function to return a Bool if Touch ID is supported.

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

Open up LoginViewController.swift.
Add another property to create a reference to class you just created.

let touchMe = TouchIDAuth()

At the bottom of viewDidLoad() add the following:

touchIDButton.isHidden = !touchMe.canEvaluatePolicy()

Here you use canEvaluatePolicy(_:error:) to check whether the device can implement Touch ID authentication. If so, then show the Touch ID button; if not, then 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 Touch ID-capable device; you’ll see the Touch ID button is displayed.

Putting Touch ID to Work

Go back to TouchIDAuthentication.swift and add a function to authenticate the user. At the bottom of the TouchIDAuth class, create the following function

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

  // 3
  context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
    localizedReason: "Logging in with Touch ID") { (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:) is going to pass a completion handler in the form of a closure back to the LoginViewController.
  2. You’re using canEvaluatePolicy() to check whether the device is Touch ID capable.
  3. If the device does support Touch ID, you then use evaluatePolicy(_:localizedReason:reply:) to begin the policy evaluation — that is, prompt the user for Touch 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.

We’ll come back and deal with errors in little while.
Switch to LoginViewController.swift and locate touchIDLoginAction(_:) by scrolling or with the jump bar.
Add the following inside the action so that it looks like this:

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

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

You can build and run to your device here, but wait! What if you haven’t set up Touch ID on your device? What if you are using the wrong finger? Let’s deal with that.

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

Dealing with Errors

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.

Switch back to TouchIDAuthentication.swift and update the authenticateUser function.

Change the signature to include an optional message that you will pass when you get an error.

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

Find the // TODO: and replace it with the LAError cases in a switch statement:

            // 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."
            default:
              message = "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.touchIDNotAvailable: the device isn’t Touch ID-compatible.
    • LAError.passcodeNotSet: there is no passcode enabled as required for Touch ID
    • LAError.touchIDNotEnrolled: there are no fingerprints stored.
  4. Pass the message in the completion closure.

iOS responds to LAError.passcodeNotSet and LAError.touchIDNotEnrolled 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 our 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)

Your finished function should look like this:

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

    guard canEvaluatePolicy() else {
      completion("Touch ID not available")
      return
    }

    context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
      localizedReason: "Logging in with Touch ID") { (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."
          default:
            message = "Touch ID may not be configured"
          }

          completion(message)
        }
    }
  }

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

  @IBAction func touchIDLoginAction(_ sender: UIButton) {

    // 1
    touchMe.authenticateUser() { 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)
      }
    }
  }
  1. We’ve added a trailing closure to pass in an optional message. If Touch ID works there is no message.
  2. We 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 Touch ID. As a bonus, you were able to have Keychain and Touch 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.

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 their password from time to time. This isn’t necessary with Touch 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.

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

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

The post How To Secure iOS User Data: The Keychain and Touch ID appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4402

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>