How To Secure iOS User Data: Keychain Services and Biometrics with SwiftUI

Learn how to integrate keychain services and biometric authentication into a simple password-protected note-taking SwiftUI app. By Bill Morefield.

4.9 (11) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Deleting a Password From the Keychain

Still in KeychainServices.swift, add the following code to the end of KeychainWrapper:

func deleteGenericPasswordFor(account: String, service: String) throws {
  // 1
  let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecAttrService as String: service
  ]

  // 2
  let status = SecItemDelete(query as CFDictionary)
  guard status == errSecSuccess || status == errSecItemNotFound else {
    throw KeychainWrapperError(status: status, type: .servicesError)
  }
}

Again, most of this code is similar to the code you added earlier to add and update an item. Note the changes:

  1. Although the query looks much like that for the add and update, here you don’t provide a new value.
    Note: Make sure the query identifies only the item or items you want to delete. If multiple items match the query, Keychain Services deletes all of them.
  2. You call SecItemDelete(_:) using the query to delete the items. There is no undo!
Note: Make sure the query identifies only the item or items you want to delete. If multiple items match the query, Keychain Services deletes all of them.

Add the following code to the top of storeGenericPasswordFor(account:service:password:) to call your new method:

if password.isEmpty {
  try deleteGenericPasswordFor(
    account: account,
    service: service)
  return
}

You can now add, retrieve, update, and delete items in a keychain. Those four methods give you the core functionality you need to work with items in your keychain.

Build and run the app and tap the reset button. The app sets the password and note to empty strings. Thanks to the changes you just made, the reset button now deletes the password as well as the note!

Rerun the app and set an initial password. Tap the lock button twice to confirm that your new password unlocks the note. Now tap the arrows button to change the password. Again, tap the lock button twice to verify that the app stored your new password.

Changing Password Prompt

Congratulations! You’ve now added Keychain Services to your app to secure the password.

Typing a password every time to view the note is tedious for the user. Next, you’ll add biometric authentication to allow simpler, but still secure, unlocking.

Biometric Authentication in SwiftUI

Biometric authentication uses unique characteristics of your body to verify your identity. Apple provides two biometric authentication methods:

  • Touch ID uses your fingerprint.
  • Face ID uses your face’s unique shape.

Both methods are faster and easier than a password. And they’re more secure. Someone might be able to guess your password, but replicating your fingerprint or face belongs in the world of spy movies rather than in reality!

In the next section, you’ll build biometric authentication into your app.

Enabling Biometric Authentication in Your App

Open ToolbarView.swift. This view defines the toolbar shown above the editor. It includes the lock button, which locks and unlocks the note. This is the perfect place to add biometric authentication to your app. You’ll use the Local Authentication framework to do this.

At the top of the file, add the following code after the existing import statement:

import LocalAuthentication

The LocalAuthentication framework allows your app access to the same systems the user has to unlock their device — namely, a passcode, Touch ID or Face ID. Currently, when the note is locked and the user taps the lock button, the app sets the showUnlockModal state property to true. Setting showUnlockModal to true displays the view that requests and validates the password.

You’ll change this to perform biometric authentication instead. If that fails, or isn’t available, you’ll fall back to the password view.

In ToolbarView.swift, add the following method above body:

func tryBiometricAuthentication() {
  // 1
  let context = LAContext()
  var error: NSError?

  // 2
  if context.canEvaluatePolicy(
    .deviceOwnerAuthenticationWithBiometrics,
    error: &error) {
    // 3
    let reason = "Authenticate to unlock your note."
    context.evaluatePolicy(
      .deviceOwnerAuthenticationWithBiometrics,
      localizedReason: reason) { authenticated, error in
      // 4
      DispatchQueue.main.async {
        if authenticated {
          // 5
          self.noteLocked = false
        } else {
          // 6
          if let errorString = error?.localizedDescription {
            print("Error in biometric policy evaluation: \(errorString)")
          }
          self.showUnlockModal = true
        }
      }
    }
  } else {
    // 7
    if let errorString = error?.localizedDescription {
      print("Error in biometric policy evaluation: \(errorString)")
    }
    showUnlockModal = true
  }
}

Here’s how each step works:

Some devices do not have authentication, or a user may not have set it up. And authentication can sometimes fail. Whatever the failure reason, you must always provide the user with an appropriate way to get in without biometric authentication!

  1. You access biometric authentication using an LAContext object. This gathers the biometrics from the user interaction and communicates with the Secure Enclave on the device. LocalAuthentication pre-dates Swift and uses Objective-C patterns like NSError.
  2. You first check that authentication is available. The first parameter, .deviceOwnerAuthenticationWithBiometrics, requests biometric authentication.
  3. You describe why you want to use authentication in reason, which iOS displays to the user. Calling evaluatePolicy(_:localizedReason:reply:) requests authentication. The device will perform either Face ID or Touch ID authentication, whichever is available on the current device. The call executes the block when it returns.
  4. Since you execute this code from a block and change the UI, you must ensure that the change runs on the UI thread.
  5. If authentication succeeded, you set the note as unlocked. Note that you get no further information about the authentication! You find out only that it succeeded or failed.
  6. If authentication failed, you print any error that came into the block. You also set the showUnlockModal state to true. This action forces your app to fall back to manual password behavior.
  7. If the initial check failed, that means biometric authentication is not available. You print the error received and then show the unlock view. Again, you provide a fallback authentication path.

    Some devices do not have authentication, or a user may not have set it up. And authentication can sometimes fail. Whatever the failure reason, you must always provide the user with an appropriate way to get in without biometric authentication!

You need one more change to enable this functionality. Press Command-F to find the // Biometric Authentication Point comment. Replace the next line, self.showUnlockModal = true, with a call to the new method:

self.tryBiometricAuthentication()

Now you’re ready to test your biometric authentication functionality!