Keychain Services API Tutorial for Passwords in Swift
In this Keychain tutorial for Swift on iOS, you’ll learn how to interact with the C language API to securely store passwords in the iOS Keychain. By Lorenzo Boaro.
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
Keychain Services API Tutorial for Passwords in Swift
30 mins
Testing the Behavior
In this section, you’ll see how to integrate unit tests for your wrapper. In particular, you’ll test the functionalities that your wrapper exposes.
Creating the Class
To create the class that will contain all your unit tests, click File ▸ New ▸ File… and select iOS ▸ Source ▸ Unit Test Case Class. On the next screen, specify the class name as SecureStoreTests, subclass XCTestCase and make sure the language is Swift. Click Next, choose the SecureStoreTests group, verify that you have selected the SecureStoreTests targets checkbox and click Create.
Xcode will prompt a dialog to create an Objective-C bridging header. Click Don’t Create to skip the creation.
Open SecureStoreTests.swift file and remove all the code within the curly braces.
Next, add the following below the import XCTest
statement:
@testable import SecureStore
This gives the unit tests access to the classes and methods defined in your SecureStore framework.
Next, add the following properties at the top of SecureStoreTests
:
var secureStoreWithGenericPwd: SecureStore!
var secureStoreWithInternetPwd: SecureStore!
Next, add a new setUp()
method like this:
override func setUp() {
super.setUp()
let genericPwdQueryable =
GenericPasswordQueryable(service: "someService")
secureStoreWithGenericPwd =
SecureStore(secureStoreQueryable: genericPwdQueryable)
let internetPwdQueryable =
InternetPasswordQueryable(server: "someServer",
port: 8080,
path: "somePath",
securityDomain: "someDomain",
internetProtocol: .https,
internetAuthenticationType: .httpBasic)
secureStoreWithInternetPwd =
SecureStore(secureStoreQueryable: internetPwdQueryable)
}
Since you test both generic and internet passwords, you create the two instances of your wrapper with two different configurations. Those configurations are the ones you developed in the previous section.
Before you forget, you’ll want to clear the state of the keychain during the tear down phase of the test so that you can start fresh for next time. Add this method to the end of the class:
override func tearDown() {
try? secureStoreWithGenericPwd.removeAllValues()
try? secureStoreWithInternetPwd.removeAllValues()
super.tearDown()
}
Since you should isolate and execute each test independently from the others, you’re going to delete all the passwords already available in the Keychain. The execution order doesn’t matter.
It’s now time to add unit tests for generic passwords.
Testing Generic Passwords
Add the following code below tearDown()
:
// 1
func testSaveGenericPassword() {
do {
try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
} catch (let e) {
XCTFail("Saving generic password failed with \(e.localizedDescription).")
}
}
// 2
func testReadGenericPassword() {
do {
try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
let password = try secureStoreWithGenericPwd.getValue(for: "genericPassword")
XCTAssertEqual("pwd_1234", password)
} catch (let e) {
XCTFail("Reading generic password failed with \(e.localizedDescription).")
}
}
// 3
func testUpdateGenericPassword() {
do {
try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
try secureStoreWithGenericPwd.setValue("pwd_1235", for: "genericPassword")
let password = try secureStoreWithGenericPwd.getValue(for: "genericPassword")
XCTAssertEqual("pwd_1235", password)
} catch (let e) {
XCTFail("Updating generic password failed with \(e.localizedDescription).")
}
}
// 4
func testRemoveGenericPassword() {
do {
try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
try secureStoreWithGenericPwd.removeValue(for: "genericPassword")
XCTAssertNil(try secureStoreWithGenericPwd.getValue(for: "genericPassword"))
} catch (let e) {
XCTFail("Saving generic password failed with \(e.localizedDescription).")
}
}
// 5
func testRemoveAllGenericPasswords() {
do {
try secureStoreWithGenericPwd.setValue("pwd_1234", for: "genericPassword")
try secureStoreWithGenericPwd.setValue("pwd_1235", for: "genericPassword2")
try secureStoreWithGenericPwd.removeAllValues()
XCTAssertNil(try secureStoreWithGenericPwd.getValue(for: "genericPassword"))
XCTAssertNil(try secureStoreWithGenericPwd.getValue(for: "genericPassword2"))
} catch (let e) {
XCTFail("Removing generic passwords failed with \(e.localizedDescription).")
}
}
There’s quite a bit going on here, so breaking it down:
-
testSaveGenericPassword()
methods verifies whether it can save a password correctly. -
testReadGenericPassword()
first saves the password then retrieves the password, checking if it’s equal to the expected one. -
testUpdateGenericPassword()
verifies when saving a different password for the same account, the latest password is the one expected after its retrieval. -
testRemoveGenericPassword()
tests that it can remove a password for a specific account. - Finally,
testRemoveAllGenericPasswords
checks that all the passwords related to a specific service are deleted from the Keychain.
Since your wrapper can throw exceptions, each catch
block makes the tests fail if something goes wrong.
Checking Your Work
Now it’s time to verify that everything works as expected. Select TestHost as the active scheme for your Xcode project:
Press Command-U on your keyboard (or select Product ▸ Test in the menu) to perform the unit tests.
Show the Test navigator and wait for the tests to execute. Once they’ve finished, you’ll expect all five tests to be green. Nice!
Next, do the same for internet passwords.
Scroll to the end of the class and just before the last curly brace add the following:
func testSaveInternetPassword() {
do {
try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
} catch (let e) {
XCTFail("Saving Internet password failed with \(e.localizedDescription).")
}
}
func testReadInternetPassword() {
do {
try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
let password = try secureStoreWithInternetPwd.getValue(for: "internetPassword")
XCTAssertEqual("pwd_1234", password)
} catch (let e) {
XCTFail("Reading internet password failed with \(e.localizedDescription).")
}
}
func testUpdateInternetPassword() {
do {
try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
try secureStoreWithInternetPwd.setValue("pwd_1235", for: "internetPassword")
let password = try secureStoreWithInternetPwd.getValue(for: "internetPassword")
XCTAssertEqual("pwd_1235", password)
} catch (let e) {
XCTFail("Updating internet password failed with \(e.localizedDescription).")
}
}
func testRemoveInternetPassword() {
do {
try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
try secureStoreWithInternetPwd.removeValue(for: "internetPassword")
XCTAssertNil(try secureStoreWithInternetPwd.getValue(for: "internetPassword"))
} catch (let e) {
XCTFail("Removing internet password failed with \(e.localizedDescription).")
}
}
func testRemoveAllInternetPasswords() {
do {
try secureStoreWithInternetPwd.setValue("pwd_1234", for: "internetPassword")
try secureStoreWithInternetPwd.setValue("pwd_1235", for: "internetPassword2")
try secureStoreWithInternetPwd.removeAllValues()
XCTAssertNil(try secureStoreWithInternetPwd.getValue(for: "internetPassword"))
XCTAssertNil(try secureStoreWithInternetPwd.getValue(for: "internetPassword2"))
} catch (let e) {
XCTFail("Removing internet passwords failed with \(e.localizedDescription).")
}
}
Notice that the code above is identical to the one previously analyzed. You’ve just replaced the reference secureStoreWithGenericPwd
with secureStoreWithInternetPwd
.
Select TestHost as the active scheme, if it’s not already, and press Command-U on your keyboard to test again. Now all the tests, both for generic and internet passwords, should be green.
Congratulations! You now have a working, stand-alone framework and unit tests in place.
Where to Go From Here?
In this tutorial, you made a framework wrapping the Keychain Services API and even integrated unit tests to prove your code works as expected. Amazing!
You could go a step further, sharing or distributing your code with other people following the final part of our tutorial Creating a Framework for iOS.
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.
If you want to learn more, check out Apple’s documentation at Keychain Services.
It’s worth noting that Keychain is not limited to passwords. You can store sensitive information like credit card data or short notes. You can also save items like cryptographic keys and certificates that you manage with Certificate, Key, and Trust Services, to conduct secure and authenticated data transactions.
What did you learn from this? Any lingering questions? Want to share something that happened along the way? You can discuss it in the forums. See you there!