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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Secure iOS User Data: Keychain Services and Biometrics with SwiftUI
30 mins
- Getting Started
- A Look at Keychain Services
- Enabling Your Keychain
- Adding a Password to the Keychain
- Searching for Keychain Items
- Using Keychain Services in SwiftUI
- Updating a Password in the Keychain
- Deleting a Password From the Keychain
- Biometric Authentication in SwiftUI
- Enabling Biometric Authentication in Your App
- Simulating Biometric Authentication in Xcode
- Simulating Touch ID Authentication
- Simulating Face ID Authentication
- Making the Authentication Method Visible to the User
- Where to go from here?
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:
- 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.
- You call
SecItemDelete(_:)
using the query to delete the items. There is no undo!
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.
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!
- 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. - You first check that authentication is available. The first parameter,
.deviceOwnerAuthenticationWithBiometrics
, requests biometric authentication. - You describe why you want to use authentication in
reason
, which iOS displays to the user. CallingevaluatePolicy(_: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. - Since you execute this code from a block and change the UI, you must ensure that the change runs on the UI thread.
- 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.
- 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. - 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!