Topics

#37: Touch ID 👍

Topics

Protecting data with Touch ID is a great way to improve your app.
Let's take a look at how to do it:

Save to Keychain

First you need to save something to the keychain, and protect it with the User Presence access control flag.

func savePassword(username: String, password: String) {
  let passwordData = password.dataUsingEncoding(NSUTF8StringEncoding)
  guard let data = passwordData else { return }

  let accessControl = SecAccessControlCreateWithFlags(
    kCFAllocatorDefault, 
    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
    .UserPresence // this part enables Touch ID support
    , nil
  )

  SecItemAdd([
    String(kSecAttrService) : serviceID,
    String(kSecClass) : kSecClassGenericPassword,
    String(kSecAttrAccessControl) : accessControl.takeUnretainedValue(),
    String(kSecAttrAccount) : username,
    String(kSecValueData) : data
  ], nil)
}

Retrieve From Keychain

Then when you retrieve, supply a prompt message and iOS will show the familiar Touch ID alert before executing the Keychain query:

func passwordForUsername(username: String) -> String? {
  let query = [
    String(kSecAttrService) : serviceID,
    String(kSecClass) : kSecClassGenericPassword,
    String(kSecReturnData) : true,
    String(kSecMatchLimit) : kSecMatchLimitOne,
    String(kSecUseOperationPrompt) : "Authenticate to Login",
    String(kSecAttrAccount) : username
  ]

  // Working with the Security Framework is still kind of ugly in Swift:

  var passwordData: Unmanaged<AnyObject>? = nil
  SecItemCopyMatching(query, &passwordData)

  if let opaque = passwordData?.toOpaque() {
    return NSString(
      data: Unmanaged<NSData>.fromOpaque(opaque).takeUnretainedValue(),
      encoding: NSUTF8StringEncoding
    ) as? String
  }

  return nil
}