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?
Apple’s Keychain Services is a mechanism for storing small, sensitive data such as passwords, encryption keys or user tokens in a secure and protected manner. Using Keychain Services, you can check that the password your user is entering matches their stored password without putting data at risk. However, entering a password is tedious! To solve this problem, Apple added biometric authentication to many devices. Biometric authentication allows users to confirm their identity quickly and securely, using either a fingerprint or a face scan.
In this tutorial, you’ll learn how to integrate Keychain Services and biometric authentication into a simple password-protected note-taking SwiftUI app.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
Open the starter project. Build and run. On the first run, the app prompts you for a password to protect your note. Go ahead and type a password into the two fields and tap Set Password. You’ll see a simple note-taking app that allows you to enter text and return to it later.
The app wraps a UITextView to manage the note. Above the editor, you’ll see three buttons. On the far left is a trash can button, which is the reset button mentioned above. On the right is one button for changing the password and another for locking and unlocking the note.
Since you just entered a new password, the note is now unlocked and ready for editing. Type some text into the text editor.
Now stop and restart the app. The app starts with the note locked. The editor window is blurred to obscure the contents. Tap the lock button and enter the password you set earlier to unlock your note. The app uses User Defaults to store your note and password.
This makes it easy for an attacker with physical access to a device to locate the password! Keychain Services provides a safer place to store the password. In the next section, you’ll begin to update the app to do this.
A Look at Keychain Services
Keychain Services provides an encrypted database managed by the operating system. It is for storing passwords, cryptographic keys, certificates or any short piece of data.
To store something in your keychain, you package several attributes about the data along with the secret information. You store all of them together into a keychain item. Keychain Services provides classes for different types of items:
-
kSecClassInternetPassword
stores a password for an internet site. -
kSecClassGenericPassword
stores a password of any type. -
kSecClassCertificate
stores a certificate. -
kSecClassKey
stores a cryptographic key item. -
kSecClassIdentity
stores an identity.
Each class uses a different set of attributes. These attributes define the information needed to identify the secured item. They also control access to the secret information. You can use the attributes to search for the item at a later time. If needed, you can share a keychain among apps.
The Keychain Services API has been around a long time. That means it’s a proven, reliable and safe place to store information. Unfortunately, it also means you’re going to deal with an API written for C! :]
But don’t panic. You’re going to write wrapper functions to let you deal with the API in a more modern fashion.
Enabling Your Keychain
Now it’s time to set up your keychain. You’ll enhance the app to add, retrieve, update, and delete a password… securely!
Adding a Password to the Keychain
In the starter project, open KeychainServices.swift in the Models group. You’ll see the definition for KeychainWrapperError
, a custom Error
that you’ll use to provide feedback to the user.
The first thing you’ll add is an initial definition for KeychainWrapper
. Insert the following code at the end of the file, after KeychainWrapperError
:
class KeychainWrapper {
func storeGenericPasswordFor(
account: String,
service: String,
password: String
) throws {
guard let passwordData = password.data(using: .utf8) else {
print("Error converting value to data.")
throw KeychainWrapperError(type: .badData)
}
}
}
You must first convert the password from a String
to Data
. If the conversion fails, you throw an error.
Don’t worry — you’ll resolve those warnings later in the tutorial. You can ignore them for now.
Don’t worry — you’ll resolve those warnings later in the tutorial. You can ignore them for now.
Access to Keychain Services works through a query. The first step in accessing Keychain Services is to create an add query. As the name implies, the add query defines the data you wish to store in the keychain.
Add the following code to the end of storeGenericPasswordFor(account:service:password:)
:
// 1
let query: [String: Any] = [
// 2
kSecClass as String: kSecClassGenericPassword,
// 3
kSecAttrAccount as String: account,
// 4
kSecAttrService as String: service,
// 5
kSecValueData as String: passwordData
]
Here’s what’s happening:
- The query is a dictionary that maps a
String
to anAny
object, depending on the attribute. This pattern is common when calling C-based APIs from Swift. For each attribute, you supply the defined global constant beginning with kSec. In each case, you cast the constant to aString
(it’s aCFString
really), and you follow it with the value for that attribute. - The fist key defines the class for this item as a generic password, using the pre-defined constant
kSecClassGenericPassword
. - For a generic password item, you provide an account, which is your username field. You passed this into the method as a parameter.
- Next, you set the service for the password. This is an arbitrary string that should reflect the purpose of the password, for example, “user login”. You also passed this into the method as a parameter.
- Finally, you set the data for the item using the
passwordData
converted from the string passed into the method.
Now that you’ve built the query, you’re ready to store the value. Add the following code after the query definition:
// 1
let status = SecItemAdd(query as CFDictionary, nil)
// 2
switch status {
// 3
case errSecSuccess:
break
// 4
default:
throw KeychainWrapperError(status: status, type: .servicesError)
}
Here’s what this code is doing:
-
SecItemAdd(_:_:)
asks Keychain Services to add information to the keychain. You cast the query to the expectedCFDictionary
type. C APIs often use the return value to show the result of a function. Here the value has typeOSStatus
. - You switch on the various values of the status code. It might seem odd to use a switch where you’re only checking one value, but who knows what might happen in the future ;]
-
errSecSuccess
means your password is now in the keychain. Your work here is done! - If
status
contains another value, the function failed.KeychainWrapperError
includes an initializer which usesSecCopyErrorMessageString(_:_:)
to create a human-readable message for the exception.
You use this same pattern to access all Keychain capabilities: First, you create a query defining the work to do, and then you call a function with that query.
You now have a method to store a password in the keychain. Next, you’ll add search functionality to find and retrieve the item you just added.