September 29, 2020

Make your own Keychain wrapper class in iOS swift

By Mohit Agrawal

Keychain API tutorial in swift

There are many ways of storing data on a user’s device like UserDefault, Core Data, file system, keychain but the question is which one to choose when you need to store some sensitive data?

The answer is Keychain. It is known as one of the most secure ways of storing data on the users’ devices. Using this apple allows you to store a small amount of sensitive data like password, credit card number, or any personal information.

What we will build?

In this iOS tutorial, I will show you how to make a keychain wrapper class so that you can use it very easily anywhere in the app.

Using this wrapper class you can easily save and retrieve any value from the keychain.

To demonstrate the use of this wrapper class, we will be making a simple login screen, on which the user will type user id and password. After clicking on the Login button, we will save the credentials in the keychain. And then the user will get the success page.

keychain ios 1

Implementation

Let’s talk about the approach of making a wrapper class for the keychain. We will make our wrapper class as a Singleton so that only one object will be created throughout the code.

Now let’s see the all the steps in detail.

New workspace

Create a new project and give it some nice name. I will be using Swift as a programming language with a storyboard.

Creation of wrapper class

Create a new swift file and name it WarmodroidKeyChain.swift. And copy the below code inside it.

import Foundation

open class WarmodroidKeyChain {
    
    open var loggingEnabled = false
    
    private init() {}
    public static let shared = WarmodroidKeyChain()
    
    open subscript(key: String) -> String? {
        get {
            return load(withKey: key)
        } set {
            DispatchQueue.global().sync(flags: .barrier) {
                self.save(newValue, forKey: key)
            }
        }
    }
    
    private func save(_ string: String?, forKey key: String) {
        let query = keychainQuery(withKey: key)
        let objectData: Data? = string?.data(using: .utf8, allowLossyConversion: false)

        if SecItemCopyMatching(query, nil) == noErr {
            if let dictData = objectData {
                let status = SecItemUpdate(query, NSDictionary(dictionary: [kSecValueData: dictData]))
                logPrint("Update status: ", status)
            } else {
                let status = SecItemDelete(query)
                logPrint("Delete status: ", status)
            }
        } else {
            if let dictData = objectData {
                query.setValue(dictData, forKey: kSecValueData as String)
                let status = SecItemAdd(query, nil)
                logPrint("Update status: ", status)
            }
        }
    }
    
    private func load(withKey key: String) -> String? {
        let query = keychainQuery(withKey: key)
        query.setValue(kCFBooleanTrue, forKey: kSecReturnData as String)
        query.setValue(kCFBooleanTrue, forKey: kSecReturnAttributes as String)
        
        var result: CFTypeRef?
        let status = SecItemCopyMatching(query, &result)
        
        guard
            let resultsDict = result as? NSDictionary,
            let resultsData = resultsDict.value(forKey: kSecValueData as String) as? Data,
            status == noErr
            else {
                logPrint("Load status: ", status)
                return nil
        }
        return String(data: resultsData, encoding: .utf8)
    }
    
    private func keychainQuery(withKey key: String) -> NSMutableDictionary {
        let result = NSMutableDictionary()
        result.setValue(kSecClassGenericPassword, forKey: kSecClass as String)
        result.setValue(key, forKey: kSecAttrService as String)
        result.setValue(kSecAttrAccessibleAlwaysThisDeviceOnly, forKey: kSecAttrAccessible as String)
        return result
    }
    
    private func logPrint(_ items: Any...) {
        if loggingEnabled {
            print(items)
        }
    }
}

WarmodroidKeyChain is a single class, you can safely call it from anywhere in the code.

To save a value in the keychain you need to do like this.

WarmodroidKeyChain.shared["Key"] = "Value"

To retrieve the value back from the keychain, do like this.

let value = WarmodroidKeyChain.shared["Key"]

Use of keychain wrapper with a demo app

We have created our keychain wrapper class in the last step. Now let’s see how to use it with a proper demo.

Open the Main.storyboard create a similar UI as shown in the below image for ViewController.swift.

keychain ios 2

Basically, It has two UITextFields and a UIButton. As soon as the user clicks on the button we will save the data entered from the TextField into the keychain. After that, we will display the SuccessViewController.

Code for ViewController.swift.

import UIKit

enum KEYS: String {
    case userID = "USER_ID"
    case password = "PASSWORD"
}

class ViewController: UIViewController {
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextFielf: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }

    @IBAction func didTapLoginButton(_ sender: Any) {
        
        let userID = emailTextField.text
        let password = passwordTextFielf.text
        
        if !((userID ?? "").isEmpty || (password ?? "").isEmpty){
            
            WarmodroidKeyChain.shared[KEYS.userID.rawValue] = userID
            WarmodroidKeyChain.shared[KEYS.password.rawValue] = password
            
            let vc = storyboard?.instantiateViewController(identifier: "SuccessViewController") as! SuccessViewController
            present(vc, animated: true, completion: nil)
            
        }
        
    }
    
}

Now you can see how I am using the wrapper class in the function didTapLoginButton(). Do try this re-usable wrapper class in your project and if you are facing any issue then please let me know it in the comment section.

Where should I learn after this?

In this article, you have created a wrapper class for the keychain. But suppose if you have share it with your other colleagues then how will you do it?

You can share the source code with them but that’s not the right approach. I will suggest you create a framework and share it using the cocoa pods.

FAQs

  1. Keychain VS Userdefaults

    Both are used to store a small amount of data only. But the twist is Keychain is more secure than the user defaults. It also provides an extra layer of security by encrypting the saved data.

Subscribe YouTube: More tutorials like this

I hope this blog post is useful for you, do let me know your opinion in the comment section below.
I will be happy to see your comments down below 👏.
Thanks for reading!!!