sec-blog-banner-lay-03

How to Use Apple‘s Touch ID Fingerprint API in Your Mobile App

Recently, we added Apple’s Touch ID fingerprint validation as a biometric login in our SecSign ID app. SecSign ID allows users to securely log into web sites and applications without using passwords.

This alternative login process prevents all password theft and means that user accounts cannot be compromised by hacking, phishing, or malware.

To help other developers, we would like to share our experiences with the integration of Apple’s Touch ID API into Objective-C iPhone applications.

Requirements for Touch ID Fingerprint Integration:

  • XCode 6
  •  iPhone 5s with iOS 8

This guide shows how to achieve three objectives:

1 Find out whether the device supports fingerprint validation and whether a fingerprint is enrolled.
2. Validate a fingerprint only.
3. Validate a fingerprint or the device’s passcode depending on the user’s choice.

The users can choose whether they want to authenticate either by fingerprint or passcode/PIN (see image below)
two_factor_authentication_touchID

1. How to determine if a user’s device supports fingerprint validation and whether a fingerprint is registered

First of all LocalAuthentication needs to be imported, which requires XCode 6:!

@import LocalAuthentication;

// Get the local authentication context:
LAContext *context = [[LAContext alloc] init];

// Test if fingerprint authentication is available on the device and a fingerprint has been enrolled.
if ([context canEvaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil])
{
    NSLog(@"Fingerprint authentication available.");
}

 

2. How to validate and verify a Touch ID fingerprint

@import LocalAuthentication;

// Get the local authentication context:
LAContext *context = [[LAContext alloc] init];

Start the fingerprint validation. This call returns immediately and does not wait for the result. Hence a function has to be supplied which will be called once the fingerprint validation is finished. Additionally, a string can be supplied which the device will display in the fingerprint view explaining the reason for the fingerprint scan:


[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"Authenticate for server login" reply:^(BOOL success, NSError *authenticationError){
    if (success) {
        NSLog(@"Fingerprint validated.");
    }
    else {
        NSLog(@"Fingerprint validation failed: %@.", authenticationError.localizedDescription);
    }
}];

Please note: If the fingerprint view of iOS appears it will cause applicationWillResignActive of the application to be called. If you have some clean-up code there as we did then you may want to move it to applicationDidEnterBackground. applicationDidEnterBackground is only called if the application has really entered the background. This will happen if the user presses the home button but not just for a fingerprint scan.

 

3. Validate a fingerprint or the device´s passcode, depending on the user’s choice.

What happens if the fingerprint validation fails? The user expects a passcode entry, much like the passcode entry to unlock the iPhone. However, using the LocalAuthentication above, the device will show an error if fingerprint validation fails. Then there is no way to start a device passcode entry afterwards. Therefore, we need an alternative approach.

Starting with iOS 8, keychain entries may have a new access condition requiring a user presence check in order to read the keychain entry. This check is actually a fingerprint validation with device passcode entry as fallback. Hence, all we have to do is to store a dummy value in the keychain and try to read it whenever we want a fingerprint scan and/or device passcode validation.

Here is the code. The first part creates the keychain entry with the user presence check access condition. The code only has to be run once.

@import LocalAuthentication;

// The identifier and service name together will uniquely identify the keychain entry.
NSString * keychainItemIdentifier = @"fingerprintKeychainEntry";
NSString * keychainItemServiceName = @"com.secsign.secsign";

// The content of the password is not important.
NSData * pwData = [@"the password itself does not matter" dataUsingEncoding:NSUTF8StringEncoding];

// Create the keychain entry attributes.
NSMutableDictionary	* attributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                                      (__bridge id)(kSecClassGenericPassword), kSecClass,
                                      keychainItemIdentifier, kSecAttrAccount,
                                      keychainItemServiceName, kSecAttrService, nil];

// Require a fingerprint scan or passcode validation when the keychain entry is read.
// Apple also offers an option to destroy the keychain entry if the user ever removes the
// passcode from his iPhone, but we don't need that option here.
CFErrorRef accessControlError = NULL;
SecAccessControlRef accessControlRef = SecAccessControlCreateWithFlags(
                                                                         kCFAllocatorDefault,
                                                                         kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                                                                         kSecAccessControlUserPresence,
                                                                         &accessControlError);

if (accessControlRef == NULL || accessControlError != NULL)
{
    NSLog(@"Cannot create SecAccessControlRef to store a password with identifier “%@” in the key chain: %@.", keychainItemIdentifier, accessControlError);
    return nil;
}

attributes[(__bridge id)kSecAttrAccessControl] = (__bridge id)accessControlRef;

// In case this code is executed again and the keychain item already exists we want an error code instead of a fingerprint scan.
attributes[(__bridge id)kSecUseNoAuthenticationUI] = @YES; 
attributes[(__bridge id)kSecValueData] = pwData;

CFTypeRef result;
OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef)attributes, &result);

if (osStatus != noErr)
{
    NSError * error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];

    NSLog(@"Adding generic password with identifier “%@” to keychain failed with OSError %d: %@.", keychainItemIdentifier, (int)osStatus, error);
}

The second part of the code shall be called whenever a fingerprint scan or device passcode validation is needed.


// Determine a string which the device will display in the fingerprint view explaining the reason for the fingerprint scan.
NSString * secUseOperationPrompt = @"Authenticate for server login";

// The keychain operation shall be performed by the global queue. Otherwise it might just nothing happen.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {

  // Create the keychain query attributes using the values from the first part of the code.
  NSMutableDictionary * query = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                          (__bridge id)(kSecClassGenericPassword), kSecClass,
                          keychainItemIdentifier, kSecAttrAccount,
                          keychainItemServiceName, kSecAttrService,
                          secUseOperationPrompt, kSecUseOperationPrompt,
                          nil];

   // Start the query and the fingerprint scan and/or device passcode validation
   CFTypeRef result = nil;
   OSStatus userPresenceStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);

    // Ignore the found content of the key chain entry (the dummy password) and only evaluate the return code.
   if (noErr == userPresenceStatus)
   {
       NSLog(@"Fingerprint or device passcode validated.");
   }
  else
  {
      NSLog(@"Fingerprint or device passcode could not be validated. Status %d.", (int) userPresenceStatus);
  }

  // To process the result at this point there would be a call to delegate method which 
  // would do its work like GUI operations in the main queue. That means it would start
  // with something like:
  //   dispatch_async(dispatch_get_main_queue(), ^{
});

You might want to add a statement somewhere which prevents the iOS 8 specific code from being called on older iOS version:


if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1)
{
    // convince the user of an iOS update
}

This is all there is to do.

See it in Action

We would love to hear your feedback. To see the code in action, please watch our YouTube demo video showcasing the world’s first Apple Touch ID web login. And you can visit our website to learn more about our current SecSign ID solution and download the current version of our app in the iTunes App Store.

If you would like to offer secure logins for your users, including Touch ID support and without using vulnerable, password-based logins, you can use the free SecSign ID API to integrate our mobile two-factor authentication with your app. And you can use our free SecSign ID plugins to integrate our secure login methodology with your website.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply