
Introduction
Starting with iOS 8, Apple provides access to the use of Touch ID technology (authentication using a fingerprint scanner embedded in the iPhone 5s) in third-party applications. In this regard, I would like to share with you detailed information about what exactly became available to developers, how to embed it in your application, what behavior it has, and also share a convenient "wrapper" that implements the most, in my opinion, likely use of Touch ID.
The required API is presented in the new LocalAuthentication framework. At the moment, its functionality is limited to interaction with the fingerprint scanner, but judging by the more general name, its feature set is likely to expand in the future. The framework does not provide any data about the user (which is generally logical), but only allows the user to be prompted to authenticate using biometrics (at the moment it is an integrated fingerprint scanner; however, itās not about the scanner in the framework, more general word biometrics). At the output, we get the status: either the authentication was successful, or something went wrong. In fact, at almost any moment in time, one can determine whether the one who uses the device really owns it.
')
This suggests using a Touch ID as additional protection when performing any important operations. For example, when confirming the transfer of funds, changing any important settings, initializing a secure chat, etc., that is, where the application should be as sure as possible that the smartphone was not in the hands of the intruder.
In order for the post to be not only readable but also reusable, I decided to describe integration with the Touch ID in the form of a āwrapperā that implements the above described scenario, which in the future can save you several hours of working time. The description is presented in the form of āproblem-solutionā, so that it is clear what is being done and for what. And so, let's get started.
Task
When performing important operations in the application, you must be able to request user authentication using the built-in biometrics tools. The need to request such authentication should be customizable by the user. You also need to take into account that the application can work on earlier versions of the operating system and on devices that lack biometrics tools.
Decision
The solution will be presented in the BiometricAuthenticationFacade class.
First of all, let's look at the most important thing - interaction with the LocalAuthentication framework. This part is hidden from the user and is not accessible from the class interface.
In a class extension, declare a property to hold the context:
@interface BiometricAuthenticationFacade () @property (nonatomic, strong) LAContext *authenticationContext; @end
Perform property initialization based on the availability of the API:
- (instancetype)init { self = [super init]; if (self) { if (self.isIOS8AndLater) { self.authenticationContext = [[LAContext alloc] init]; } } return self; }
Next, we define a method that will return the availability of using local authentication:
- (BOOL)isPassByBiometricsAvailable { return [self.authenticationContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:NULL]; }
As a parameter, the
canEvaluatePolicy:error:
method takes a type of local authentication. Currently only one type of
LAPolicyDeviceOwnerAuthenticationWithBiometrics
, which speaks for itself. Using biometrics may not be available if the device does not physically support this feature, or if the user has not enabled this feature in the smartphone settings.
A request to perform a user's fingerprint scanning will be described as follows:
- (void)passByBiometricsWithReason:(NSString *)reason succesBlock:(void(^)())successBlock failureBlock:(void(^)(NSError *error))failureBlock { [self.authenticationContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:^(BOOL success, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success) { successBlock(); } else { failureBlock(error); } }); }]; }
As parameters, the
evaluatePolicy:localizedReason:reply:
method takes the above described type of local authentication, a message that should briefly describe the reason for the request, and a block that will be asynchronously executed after the completion of the entire procedure.
Please note that the execution of the
reply
block on the main stream is not guaranteed (in fact it is not called on the main one), therefore the
dispatch_async
call is added. It would be possible to leave it as it is, but most developers assume that the block that is passed to the method called on the main thread will also be called on the main thread, and do not put an additional check. It happened so historically.
When calling the above described method, the system displays the dialog:

- The title uses the application name (
CFBundleDisplayName
); - The string specified as the
localizedReason
parameter; - With this field is not so simple. When it is pressed, the dialog for entering the password will not appear, as you might have thought, but instead the
reply
block will be called with an error. The error code is documented:
LAErrorUserFallback
Authentication button has been canceled because the user has tapped the fallback button (Enter Password).
That is, it was intended. As the user Flanker_4 explained, the system thus offers the application to independently perform alternative authentication: entering the application password. That is, the implementation of dialogue and logic for entering and checking a password lies on the shoulders of the developer; - Button to cancel the request. As a result, the
reply
block will be called with the corresponding LAErrorUserCancel
error.
If the scan was successful, the reply block will be called with a positive result.
It should be noted that the dialog for scanning is not displayed every time the
evaluatePolicy:localizedReason:reply:
method is called. That is, the success of the last scan has a certain lifetime. Repeated authentication attempt within a few minutes will result in an instant call to the
reply
block with a positive result.
If you use the wrong finger and try to scan it 5 times in a row, the system will offer to enter the password specified in the smartphone settings:

For clarity, I will clarify that it is impossible to turn on the scanner in the smartphone settings without creating a password.
After the user enters the correct password, he will again be prompted to scan a fingerprint. But, as the user of
simplix noted , if you know the device password, then the scanner can be disabled in the global settings of the device. In fact, re-displaying the dialog for scanning does not make sense.
This completes the interaction with
LocalAuthentication
.
Let us turn to the implementation of the interface of our facade.
Method to find out the availability of authentication. The result is determined by the availability of the API and the scanner:
- (BOOL)isAuthenticationAvailable { return self.isIOS8AndLater && self.isPassByBiometricsAvailable; }
A method for determining whether authentication is enabled for an operation:
- (BOOL)isAuthenticationEnabledForFeature:(NSString *)featureName { return self.isAuthenticationAvailable && [self loadIsAuthenticationEnabledForFeature:featureName]; }
An example of a transaction could be access to settings, a cash transaction, etc.
Power-on state is stored in NSUserDefaults. Below is the implementation of the
loadIsAuthenticationEnabledForFeature:
method
loadIsAuthenticationEnabledForFeature:
Method to enable authentication for a specific operation:
- (void)enableAuthenticationForFeature:(NSString *)featureName succesBlock:(void(^)())successBlock failureBlock:(void(^)(NSError *error))failureBlock { if (self.isAuthenticationAvailable) { if ([self isAuthenticationEnabledForFeature:featureName]) { successBlock(); } else { [self saveIsAuthenticationEnabled:YES forFeature:featureName]; successBlock(); } } else { failureBlock(self.authenticationUnavailabilityError); } }
The method is necessary so that the user of the application has the opportunity to independently determine operations for which additional verification is necessary.
The enable state is stored in
NSUserDefaults
. Below is an implementation of the
saveIsAuthenticationEnabled:forFeature
.
Authentication off method for a specific operation:
- (void)disableAuthenticationForFeature:(NSString *)featureName withReason:(NSString *)reason succesBlock:(void(^)())successBlock failureBlock:(void(^)(NSError *error))failureBlock { if (self.isAuthenticationAvailable) { if ([self isAuthenticationEnabledForFeature:featureName]) { [self passByBiometricsWithReason:reason succesBlock:^{ [self saveIsAuthenticationEnabled:NO forFeature:featureName]; successBlock(); } failureBlock:failureBlock]; } else { successBlock(); } } else { failureBlock(self.authenticationUnavailabilityError); } }
As you can see, to turn off, you need to make sure that we are dealing with the owner of the smartphone, and not the attacker.
The method of requesting user authentication to access the operation:
- (void)authenticateForAccessToFeature:(NSString *)featureName withReason:(NSString *)reason succesBlock:(void(^)())successBlock failureBlock:(void(^)(NSError *error))failureBlock { if (self.isAuthenticationAvailable) { if ([self isAuthenticationEnabledForFeature:featureName]) { [self passByBiometricsWithReason:reason succesBlock:successBlock failureBlock:failureBlock]; } else { successBlock(); } } else { failureBlock(self.authenticationUnavailabilityError); } }
Methods for storing and retrieving information about the need to authenticate a user to access an operation (not available from the class interface):
- (void)saveIsAuthenticationEnabled:(BOOL)isAuthenticationEnabled forFeature:(NSString *)featureName { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *featuresDictionary = nil; NSDictionary *currentDictionary = [userDefaults valueForKey:kFeaturesDictionaryKey]; if (currentDictionary == nil) { featuresDictionary = [NSMutableDictionary dictionary]; } else { featuresDictionary = [NSMutableDictionary dictionaryWithDictionary:currentDictionary]; } [featuresDictionary setValue:@(isAuthenticationEnabled) forKey:featureName]; [userDefaults setValue:featuresDictionary forKey:kFeaturesDictionaryKey]; [userDefaults synchronize]; } - (BOOL)loadIsAuthenticationEnabledForFeature:(NSString *)featureName { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSDictionary *featuresDictionary = [userDefaults valueForKey:kFeaturesDictionaryKey]; return [[featuresDictionary valueForKey:featureName] boolValue]; }
NSUserDefaults
used as storage. All data is stored in a separate dictionary to reduce the likelihood of conflicts in the names of keys.
This is where the main implementation of the facade ends.
Ending
And finally, for those who have mastered reading to the end, some interesting facts about the scanner in the iPhone 5s:
- The probability of a false pass, i. the fact that the imprint of a random person will be recognized as yours is 1 in 50,000;
- The system allows you to perform 5 scanning attempts before a user password is requested. Thus, a brute-force attack cannot be performed, and the probability that a scanner can be hacked by an attacker is ā0.0001;
- The scanner captures a bitmap image with a size of 88x88 pixels and a density of 500 ppi. The resulting raster image is converted into a vector and is subjected to additional analysis;
- The resulting fingerprint data is stored in encrypted form in a special area (Secure Enclave) on the A7 processor. The data is encrypted with a private key that is generated and written to Secure Enclave during the production of the processor in the factory. Apple claims that neither the encrypted data nor the private key leaves the mobile device and are unknown to third parties, including Apple itself.
Source of interesting facts:
iOS SecurityThe full version of the source code is available on GitHub:
BiometricAuthenticationFacade