Group Group Group Group Group Group Group Group Group

raywenderlich.com Forums

How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID

Learn how to use the keychain and biometrics to secure your app and use Face ID or Touch ID.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/236-how-to-secure-ios-user-data-the-keychain-and-biometrics-face-id-or-touch-id

Biometric authentication is an interesting process on iOS, and your article presents a great introduction to it. That said, there are a few additional issues of which developers should be aware.

“All of this means you can comfortably hand over the responsibility of handling login information to the Keychain…”

This is perhaps the biggest problem. It should be noted that tools exist to examine and dump an unlocked keychain. As such, it’s highly recommended passwords and other confidential information be encrypted by the application PRIOR to storing such data in the keychain, and decrypting it upon retrieval.

http://resources.infosecinstitute.com/ios-application-security-part-12-dumping-keychain-data/#gref

Failure to encrypt confidential data going into the keychain can and will be flagged as a security issue should your app ever undergo penetration testing.

Also, Apple’s sample keychain code fails to set kSecAttrAccessible. This variable is used to specify when the application needs access to that data. Developers should be careful about keychain access and always use the most restrictive option suitable to their needs.

Next, point is that LABiometryType is only available on iOS 11. If you app supports iOS 10 or lower, you’ll need to wrap your LABiometryType check in a #available(iOS 11.0, *) condition test.

Finally, as of iOS 10 canEvaluatePolicy() will return LAErrorTouchIDLockout if the user has too many failed authentication attempts. More to the point, the system will no longer automatically present the PIN entry screen to correct this problem.

If you want to allow the user to correct touch/face id lockout issues, you need to check canEvaluatePolicy() for an LAErrorTouchIDLockout error, and then retry the process using LAPolicyDeviceOwnerAuthentication.

canEvaluatePolicy() may also return LAErrorTouchIDLockout, making it a somewhat problematic function call. If locked out, biometric authentication IS available on the device, but canEvaluatePolicy() will fail as mentioned above (even though pin access is available).

Developers should be also aware that Apple may lie to the app about whether or not biometric authentication is available on a given device. Especially if the user has previously blocked app access.

1 Like

Thanks again for your sage advice. I believe I mentioned that users should checkout Rob Napier’s talk at 360 iDev on Practical Security. In particular on handling passwords as user input. He suggests encrypting the password immediately to be safe. I’m not sure if that made it through the editing passes. It’s also beyond the scope of this tutorial which is meant as an introduction. It’s also written in a friendly tone so as to not scare off readers.

Still your input is welcome as always.

I´m getting this error “Initializer for conditional binding must have Optional type, not ‘String’” in this line “if let message = message {…” and I don´t understand why, help please.

This is the complete method.

@IBAction func touchIDLoginAction() {
// 1
touchMe.authenticateUser() { [weak self] message in
// 2
if let message = message {
// if the completion is not nil show an alert
let alertView = UIAlertController(title: “Error”,
message: message,
preferredStyle: .alert)
let okAction = UIAlertAction(title: “Darn!”, style: .default)
alertView.addAction(okAction)
self?.present(alertView, animated: true)
} else {
// 3
self?.performSegue(withIdentifier: “dismissLogin”, sender: self)
}
}
}

@rickter The above code works fine for me. The error suggests that you are trying to unwrap the message variable which is of type String instead of String? in this case.

Try removing the if let statement and see what happens. Please let me know if you have any other questions or issues about the whole thing. Thank you! :]

Thanks to you I can fix my mistake, I missed “?” in the method declaration.

“func authenticateUser(completion: @escaping (String?) -> Void)…”

I also tried to reproduce your error, but I couldn’t. Can you check your code before this or anything that calls it?

Can you press the Option Key and click on “message” on each side of the equals sign? It should be an Optional on the right side. What the error says is that it is typed as a String (or is not) i can’t see, since I’m answering on my phone

So somewhere earlier in your app, it is being typed as a actual String. Not an Optional of String Type (eg String?). Does that make sense to you?

Oops. Just saw your reply. Glad you fixed it. :wink: Five points to Gryffindor!

Great article, if you have more than one first level view controller, think a Tab View Controller, where do you invoke the logIn screen from if hte app goes into the background?

I think I have answered my own question. I’d have to add both NotificationCenter observers and the unwindSegue, viewDidAppear, appWillResignActive, appDidBecomeActive and logoutAction methods to each viewController. In your experience could I move these items to a separate Class and call the new Class from the ViewController’s viewDidLoad?

correct. In appWillResignActive we reset the isAuthenticated to false. Then when we comeback to the app appDidBecomeActive shows the Login View again.

So I created a segue from the DetailViewController to the LoginViewController and created an unwind segue back to the DetailViewControler. However, I can’t get the LoginViewController to take me back to the DetailViewController with the Note I had open when I send it into the background. if I send the demo application to the background, it always takes me back to the MasterViewController from the LoginViewController.

I set a string attribute on the LoginViewController to identify which unwind segue to call and in the prepare for segue form each controller I send in the unwind segue name. I also set teh segue.destination in the prepare for segue of each VeiwController to identify where to go, the LoginViewController

How do I get it to go back to the DetailViewController it came from?

Please check out this tutorial when you get a chance:

Thank you! :]

Hi, is it possible for Multiple Users in one Device and one Apps?
Plese, I need to know.

I have a plan to develop for enterprise.
Thank you

It would be possible to store multiple combinations of username and passwords using the keychain. In that case you’d want to separate the data stored for each user.

You can store up to five finger prints with Touch ID so you could store different user’s fingerprints. It’s generally discouraged to do that since without some extra checks there’s no way to separate which user uses which fingerprint.

Face ID only stores one “face print”. So, it is not possible to store for logins multiple users on the same device.

Thank you for the tutorial update and thank you hmlongvo for your reply regarding security. Wenderlich staff, please consider making security concerns mandatory in all tutorials. Just as security should be incorporated into development, it should take a front seat in these tutorials that developers rely on. You could have a very positive impact on iOS development by doing this. Please consider it, it is so important and many of the developers learning through you may not have access to the security expertise that some who are employed by larger or more technically savvy companies may take for granted. If you are a developer, do yourself a favor and look at the OWASP site:
https://www.owasp.org/index.php/IOS_Developer_Cheat_Sheet
https://www.owasp.org/index.php/IOS_Application_Security_Testing_Cheat_Sheet

1 Like

First of all, thank you for this excellent tutorial – it helped me a lot. :wink:

I think I found two small mistakes in the code:

  1. When an user cancels the popup prompting to log in with Touch ID performSegue(withIdentifier: "loginView", sender: self) is invoked once again.
  2. Error handling inside the authenticateUser(completion:) method in the TouchIDAuthentication class should also be placed inside DispatchQueue.main.async because, a moment later, we present an UIViewController with the message from the completion handler