Authenticating with Touch ID and the iPhone pin

One thing I really love about the new iOS devices - iPhone 5S, 6, 6+ and the iPad Air 2 - is the Touch ID sensor. I’ve always had a PIN on my devices - there is too much important information on my phone to not have one - and TouchI ID takes the pain out of it[1]. It's also core to the iOS experience: Apple Pay relies on it, and it's used by iTunes and a lot of other Apple apps.

Touch ID promo shot

But one thing I want is a nice, easy way to use the Touch ID sensor in my own apps. iOS8 introduced the Local Authentication APIs. This makes it easy - trivial - to request a Touch ID authentication.

var context = new LAContext ();
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Do Secret Stuff", (bool success, NSError error) => {
	if (success) {
		//yay
	} else {
		switch (error.Code) {
		case LAStatus.AuthenticationFailed:
			break;
		case LAStatus.UserCancel:
			break;
		//etc
		}
	}
});

This is all good and easy, but it gives the user an option I don't like: they can use Touch ID, or they can enter a password (or cancel). And if there is no Touch ID, it'll just not work - it's finger prints or nothing. Worse, there is no way to turn off the password option (or change the text).

Touch ID shot using LAContext

One thing I didn't know about until recently is that you can get the same - or similar - prompt, but allow it to fall back to the devices PIN, without a password option. This means it works on any iOS8 device.

For most of my uses - validate that the person on the phone is the owner or someone the owner has trusted - this is the best option.

It's just not that obvious how to do it.

The general idea for this - and I'm assuming it's a bit of a hack workaround - is to put a new item into the Keychain, but set it's ACLs to require the user to authenticate in order to get it back. The magic ACLs are

new SecAccessControl (SecAccessible.WhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.UserPresence)

In Objective-C land, this is kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly and kSecAccessControlUserPresence. For the life of me, I can't find reference to it in Apple's docs, but they do have a sample and also slides from a WWDC session on this. Maybe we get the docs in iOS9.

The full call to create the Keychain item is this:

var secret = NSData.FromString (UIDevice.CurrentDevice.IdentifierForVendor.ToString(), NSStringEncoding.Unicode);

var record = new SecRecord (SecKind.GenericPassword) {
	Service = NSBundle.MainBundle.BundleIdentifier,
	Account = "SecurityViewAccount",
	AccessControl = new SecAccessControl (SecAccessible.WhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.UserPresence),
	UseNoAuthenticationUI = true,
	ValueData = secret
};

var res = SecKeyChain.Add (record);

return (res == SecStatusCode.Success);

You can update it in a similar manner if needed, as the add will fail if the item already exists. To do the actual authentication:

var query = new SecRecord (SecKind.GenericPassword) {
	Service = NSBundle.MainBundle.BundleIdentifier,
	Account = "SecurityViewAccount",
	AccessControl = new SecAccessControl (SecAccessible.WhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.UserPresence),
	UseOperationPrompt = "Your message goes here", 
};
SecStatusCode status;

var res = SecKeyChain.QueryAsData (query, false, out status);
if (res != null) {
	return NSString.FromData(res, NSStringEncoding.Unicode).ToString ();
}

return null;

A non-null result means they authenticated - if you need it, the result is the secret that you stored when you created the item. This shows a subtley different UI to the user:

Touch ID UI with PIN

And better yet, on iOS8 devices without Touch ID, the user is prompted for the PIN if they press "Enter Passcode".

There is still no customisation of the dialog. No option to ONLY use Touch ID (no PIN), which would be nice. And no fallback for devices which have no PIN set at all - you'd need to make your own PIN screen for that.

I've put together a very basic project which shows how it works. It's iOS8 only, tho it works on iOS7 - it just will not let you in! When I get around to rewriting Trip Wallet, I think I'll use this as the main authentication method.



  1. The only thing I don't like is, sometimes, when I want to hit the media controls, the phone has already logged me in. First world problem, I know. ↩︎