šŸ“œ ā¬†ļø ā¬‡ļø

We build Touch ID in iOS application



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:

  1. The title uses the application name ( CFBundleDisplayName );
  2. The string specified as the localizedReason parameter;
  3. 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;
  4. 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:


Source of interesting facts: iOS Security
The full version of the source code is available on GitHub: BiometricAuthenticationFacade

Source: https://habr.com/ru/post/235699/


All Articles