FaceID or TouchID Authentication
Dear Folks, today I am going to explain the proper way to integrate the Face ID or Touch ID in iOS App. Face ID and Touch ID are secure, familiar authentication methods that people trust. Follow below steps:
Step 1: Add NSFaceIDUsageDescription Key in info.plist file
<key>NSFaceIDUsageDescription</key>
<string>This app requires Face ID permission to authenticate using Face recognition.</string>
Step 2: Create a swift file with any name but it would be better to related to its functionality, Like BioMetrixFile.swift
Step 3: In BioMetrixFile write below codes and follow instructions. But before proceeding further import LocalAuthentication class in BioMetrixFile.
Now, create typealias for success or failure blocks to well manage Authentication Success or Failure.
public typealias AuthenticationSuccess = (() -> ())
public typealias AuthenticationFailure = ((AuthenticationError) -> ())
Now written all string messages success or error, created enum for them.
let kBiometryNotAvailableReason = "BioMetrix authentication is not available for this device."
enum BioMetrixTouchIDErrors: String {
//Touch ID
case kTouchIdAuthenticationReason = "Confirm your fingerprint to authenticate."
case kTouchIdPasscodeAuthenticationReason = "Touch ID is locked now, because of too many failed attempts. Enter passcode to unlock Touch ID."
/// Error Messages Touch ID
case kSetPasscodeToUseTouchID = "Please set device passcode to use Touch ID for authentication."
case kNoFingerprintEnrolled = "There are no fingerprints enrolled in the device. Please go to Device Settings -> Touch ID & Passcode and enroll your fingerprints."
case kDefaultTouchIDAuthenticationFailedReason = "Touch ID does not recognise your fingerprint. Please try again with your enrolled fingerprint."
}
enum BioMetrixFaceIDErrors: String{
//Face ID
case kFaceIdAuthenticationReason = "Confirm your face to authenticate."
case kFaceIdPasscodeAuthenticationReason = "Face ID is locked now, because of too many failed attempts. Enter passcode to unlock Face ID."
// Error Messages Face ID
case kSetPasscodeToUseFaceID = "Please set device passcode to use Face ID for authentication."
case kNoFaceIdentityEnrolled = "There is no face enrolled in the device. Please go to Device Settings -> Face ID & Passcode and enroll your face."
case kDefaultFaceIDAuthenticationFailedReason = "Face ID does not recognise your face. Please try again with your enrolled face."
}
Step 4: Created a Singleton class with name BioMetrixAuthentication which is subclass of NSObject.
open class BioMetrixAuthentication: NSObject {
public static let shared = BioMetrixAuthentication()
}
Step 5: Added a class function to check whether device has TouchID available or not.
class func canAuthenticate() -> Bool {
var isBioMetrixAuthenticationAvailable = false
var error: NSError? = nil
if LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
isBioMetrixAuthenticationAvailable = (error == nil)
}
return isBioMetrixAuthenticationAvailable
}
Step 6: Added a function to Check BioMetrix Authentication (FaceId / Touch Id)
class func authenticateWithBioMetrixs(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultBioMetrixAuthenticationReason() : reason
let context = LAContext()
context.localizedFallbackTitle = fallbackTitle
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
Step 7: Add a function to check device passcode if user fails to authenticate himself using touchID or faceID several times.
/// Check for device passcode authentication
class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultPasscodeAuthenticationReason() : reason
let context = LAContext()
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
if #available(iOS 9.0, *) {
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthentication, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
} else {
// Fallback on earlier versions
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
}
Step 8: Add a method to check if device supports faceID or not.
public func faceIDAvailable() -> Bool {
if #available(iOS 11.0, *) {
let context = LAContext()
return (context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil) && context.biometryType == .faceID)
}
return false
}
Step 9: Add a method to show authentication message regarding faceID or TouchId. If device supports faceID, it will pick the value from BioMetrixFaceIDError Enum otherwise from BioMetrixTouchIDErrors Enum.
func defaultBioMetrixAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdAuthenticationReason.rawValue
}
Step 10. Now Add a method to handle the error message, when user has too many failed attempts for faceID or TouchId.
func defaultPasscodeAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
}
Step 11. Now Add method to evaluate the specified policy for faceID or TouchID. More info: https://developer.apple.com/documentation/localauthentication/lacontext
func evaluate(policy: LAPolicy, with context: LAContext, reason: String, success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
context.evaluatePolicy(policy, localizedReason: reason) { (success, err) in
DispatchQueue.main.async {
if success { successBlock() }
else {
let errorType = AuthenticationError.initWithError(err as! LAError)
failureBlock(errorType)
}
}
}
}
Step 12: Add AuthenticationError enum to manage errors.
public enum AuthenticationError {
case failed, canceledByUser, fallback, canceledBySystem, passcodeNotSet, biometryNotAvailable, biometryNotEnrolled, biometryLockedout, other
public static func initWithError(_ error: LAError) -> AuthenticationError {
switch Int32(error.errorCode) {
case kLAErrorAuthenticationFailed:
return failed
case kLAErrorUserCancel:
return canceledByUser
case kLAErrorUserFallback:
return fallback
case kLAErrorSystemCancel:
return canceledBySystem
case kLAErrorPasscodeNotSet:
return passcodeNotSet
case kLAErrorBiometryNotAvailable:
return biometryNotAvailable
case kLAErrorBiometryNotEnrolled:
return biometryNotEnrolled
case kLAErrorBiometryLockout:
return biometryLockedout
default:
return other
}
}
// get error message based on type
public func message() -> String {
let authentication = BioMetrixAuthentication.shared
switch self {
case .canceledByUser, .fallback, .canceledBySystem:
return ""
case .passcodeNotSet:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kSetPasscodeToUseFaceID.rawValue : BioMetrixTouchIDErrors.kSetPasscodeToUseTouchID.rawValue
case .biometryNotAvailable:
return kBiometryNotAvailableReason
case .biometryNotEnrolled:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kNoFaceIdentityEnrolled.rawValue : BioMetrixTouchIDErrors.kNoFingerprintEnrolled.rawValue
case .biometryLockedout:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
default:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kDefaultFaceIDAuthenticationFailedReason.rawValue : BioMetrixTouchIDErrors.kDefaultTouchIDAuthenticationFailedReason.rawValue
}
}
}
If you followed all the steps mentioned above, your BioMetrixFile.swift will look like this.
import UIKit
import LocalAuthentication
public typealias AuthenticationSuccess = (() -> ())
public typealias AuthenticationFailure = ((AuthenticationError) -> ())
let kBiometryNotAvailableReason = "BioMetrix authentication is not available for this device."
enum BioMetrixTouchIDErrors: String {
//Touch ID
case kTouchIdAuthenticationReason = "Confirm your fingerprint to authenticate."
case kTouchIdPasscodeAuthenticationReason = "Touch ID is locked now, because of too many failed attempts. Enter passcode to unlock Touch ID."
/// Error Messages Touch ID
case kSetPasscodeToUseTouchID = "Please set device passcode to use Touch ID for authentication."
case kNoFingerprintEnrolled = "There are no fingerprints enrolled in the device. Please go to Device Settings -> Touch ID & Passcode and enroll your fingerprints."
case kDefaultTouchIDAuthenticationFailedReason = "Touch ID does not recognise your fingerprint. Please try again with your enrolled fingerprint."
}
enum BioMetrixFaceIDErrors: String{
//Face ID
case kFaceIdAuthenticationReason = "Confirm your face to authenticate."
case kFaceIdPasscodeAuthenticationReason = "Face ID is locked now, because of too many failed attempts. Enter passcode to unlock Face ID."
// Error Messages Face ID
case kSetPasscodeToUseFaceID = "Please set device passcode to use Face ID for authentication."
case kNoFaceIdentityEnrolled = "There is no face enrolled in the device. Please go to Device Settings -> Face ID & Passcode and enroll your face."
case kDefaultFaceIDAuthenticationFailedReason = "Face ID does not recognise your face. Please try again with your enrolled face."
}
open class BioMetrixAuthentication: NSObject {
// MARK: - Singleton
public static let shared = BioMetrixAuthentication()
}
// MARK:- Public
public extension BioMetrixAuthentication {
/// checks if TouchID or FaceID is available on the device.
class func canAuthenticate() -> Bool {
var isBioMetrixAuthenticationAvailable = false
var error: NSError? = nil
if LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
isBioMetrixAuthenticationAvailable = (error == nil)
}
return isBioMetrixAuthenticationAvailable
}
/// Check for BioMetrix authentication
class func authenticateWithBioMetrixs(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultBioMetrixAuthenticationReason() : reason
let context = LAContext()
context.localizedFallbackTitle = fallbackTitle
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
/// Check for device passcode authentication
class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultPasscodeAuthenticationReason() : reason
let context = LAContext()
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
if #available(iOS 9.0, *) {
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthentication, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
} else {
// Fallback on earlier versions
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
}
/// checks if face id is avaiable on device
public func faceIDAvailable() -> Bool {
if #available(iOS 11.0, *) {
let context = LAContext()
return (context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil) && context.biometryType == .faceID)
}
return false
}
}
// MARK:- Private
extension BioMetrixAuthentication {
/// get authentication reason to show while authentication
func defaultBioMetrixAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdAuthenticationReason.rawValue
}
/// get passcode authentication reason to show while entering device passcode after multiple failed attempts.
func defaultPasscodeAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
}
/// evaluate policy
func evaluate(policy: LAPolicy, with context: LAContext, reason: String, success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
context.evaluatePolicy(policy, localizedReason: reason) { (success, err) in
DispatchQueue.main.async {
if success { successBlock() }
else {
let errorType = AuthenticationError.initWithError(err as! LAError)
failureBlock(errorType)
}
}
}
}
}
//----- Validations Errors // Success ------
public enum AuthenticationError {
case failed, canceledByUser, fallback, canceledBySystem, passcodeNotSet, biometryNotAvailable, biometryNotEnrolled, biometryLockedout, other
public static func initWithError(_ error: LAError) -> AuthenticationError {
switch Int32(error.errorCode) {
case kLAErrorAuthenticationFailed:
return failed
case kLAErrorUserCancel:
return canceledByUser
case kLAErrorUserFallback:
return fallback
case kLAErrorSystemCancel:
return canceledBySystem
case kLAErrorPasscodeNotSet:
return passcodeNotSet
case kLAErrorBiometryNotAvailable:
return biometryNotAvailable
case kLAErrorBiometryNotEnrolled:
return biometryNotEnrolled
case kLAErrorBiometryLockout:
return biometryLockedout
default:
return other
}
}
// get error message based on type
public func message() -> String {
let authentication = BioMetrixAuthentication.shared
switch self {
case .canceledByUser, .fallback, .canceledBySystem:
return ""
case .passcodeNotSet:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kSetPasscodeToUseFaceID.rawValue : BioMetrixTouchIDErrors.kSetPasscodeToUseTouchID.rawValue
case .biometryNotAvailable:
return kBiometryNotAvailableReason
case .biometryNotEnrolled:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kNoFaceIdentityEnrolled.rawValue : BioMetrixTouchIDErrors.kNoFingerprintEnrolled.rawValue
case .biometryLockedout:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
default:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kDefaultFaceIDAuthenticationFailedReason.rawValue : BioMetrixTouchIDErrors.kDefaultTouchIDAuthenticationFailedReason.rawValue
}
}
}
Now moving to the UIViewController, where you want to integrate the FaceID or TouchID. Create UIButton IBAction method and follow below proper steps to manage the correct flow of authentication. Adding below lines of code in IBAction method of this button.
Steps 13: Starting Authentication by calling the class function of BioMetrixAuthentication Class in BioMetrixFile.swift file.
// start authentication
BioMetrixAuthentication.authenticateWithBioMetrixs(reason: "", success: {
// authentication successful
self.showLoginSucessAlert()
}, failure: { [weak self] (error) in
// do nothing on canceled
if error == .canceledByUser || error == .canceledBySystem {
return
}
// device does not support biometric (face id or touch id) authentication
else if error == .biometryNotAvailable {
print("Show Error: ", error.message())
}
// show alternatives on fallback button clicked
else if error == .fallback {
// here we're entering username and password
print("Normal login with username & password, make textfield first responder")
}
// No biometry enrolled in this device, ask user to register fingerprint or face
else if error == .biometryNotEnrolled {
self?.showGotoSettingsAlert(message: error.message())
}
// Biometry is locked out now, because there were too many failed attempts.
// Need to enter device passcode to unlock.
else if error == .biometryLockedout {
self?.showPasscodeAuthentication(message: error.message())
}
// show error on authentication failed
else {
print("Show Error: ", error.message())
}
})
Step 14: Add method to show passcode authentication
// show passcode authentication
func showPasscodeAuthentication(message: String) {
BioMetrixAuthentication.authenticateWithPasscode(reason: message, success: {
// passcode authentication success
self.showLoginSucessAlert()
}) { (error) in
print("Show Error: ", error.message())
}
}
Step 15: Adding a common method for login success.
func showLoginSucessAlert(){
print("Login Success")
}
Step 16: Handling the situation, when user has not enabled FaceId or TouchId.
func showGotoSettingsAlert(message: String){
let alert = UIAlertController(title: "Go to settings", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (self) in
let url = URL(string: "App-Prefs:root=TOUCHID_PASSCODE")
if UIApplication.shared.canOpenURL(url!) {
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
I would love to hear from you, if you have any doubt regarding code please leave comment below.
Dear Folks, today I am going to explain the proper way to integrate the Face ID or Touch ID in iOS App. Face ID and Touch ID are secure, familiar authentication methods that people trust. Follow below steps:
Step 1: Add NSFaceIDUsageDescription Key in info.plist file
<key>NSFaceIDUsageDescription</key>
<string>This app requires Face ID permission to authenticate using Face recognition.</string>
Step 2: Create a swift file with any name but it would be better to related to its functionality, Like BioMetrixFile.swift
Step 3: In BioMetrixFile write below codes and follow instructions. But before proceeding further import LocalAuthentication class in BioMetrixFile.
Now, create typealias for success or failure blocks to well manage Authentication Success or Failure.
public typealias AuthenticationSuccess = (() -> ())
public typealias AuthenticationFailure = ((AuthenticationError) -> ())
Now written all string messages success or error, created enum for them.
let kBiometryNotAvailableReason = "BioMetrix authentication is not available for this device."
enum BioMetrixTouchIDErrors: String {
//Touch ID
case kTouchIdAuthenticationReason = "Confirm your fingerprint to authenticate."
case kTouchIdPasscodeAuthenticationReason = "Touch ID is locked now, because of too many failed attempts. Enter passcode to unlock Touch ID."
/// Error Messages Touch ID
case kSetPasscodeToUseTouchID = "Please set device passcode to use Touch ID for authentication."
case kNoFingerprintEnrolled = "There are no fingerprints enrolled in the device. Please go to Device Settings -> Touch ID & Passcode and enroll your fingerprints."
case kDefaultTouchIDAuthenticationFailedReason = "Touch ID does not recognise your fingerprint. Please try again with your enrolled fingerprint."
}
enum BioMetrixFaceIDErrors: String{
//Face ID
case kFaceIdAuthenticationReason = "Confirm your face to authenticate."
case kFaceIdPasscodeAuthenticationReason = "Face ID is locked now, because of too many failed attempts. Enter passcode to unlock Face ID."
// Error Messages Face ID
case kSetPasscodeToUseFaceID = "Please set device passcode to use Face ID for authentication."
case kNoFaceIdentityEnrolled = "There is no face enrolled in the device. Please go to Device Settings -> Face ID & Passcode and enroll your face."
case kDefaultFaceIDAuthenticationFailedReason = "Face ID does not recognise your face. Please try again with your enrolled face."
}
Step 4: Created a Singleton class with name BioMetrixAuthentication which is subclass of NSObject.
open class BioMetrixAuthentication: NSObject {
public static let shared = BioMetrixAuthentication()
}
Step 5: Added a class function to check whether device has TouchID available or not.
class func canAuthenticate() -> Bool {
var isBioMetrixAuthenticationAvailable = false
var error: NSError? = nil
if LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
isBioMetrixAuthenticationAvailable = (error == nil)
}
return isBioMetrixAuthenticationAvailable
}
Step 6: Added a function to Check BioMetrix Authentication (FaceId / Touch Id)
class func authenticateWithBioMetrixs(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultBioMetrixAuthenticationReason() : reason
let context = LAContext()
context.localizedFallbackTitle = fallbackTitle
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
Step 7: Add a function to check device passcode if user fails to authenticate himself using touchID or faceID several times.
/// Check for device passcode authentication
class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultPasscodeAuthenticationReason() : reason
let context = LAContext()
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
if #available(iOS 9.0, *) {
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthentication, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
} else {
// Fallback on earlier versions
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
}
Step 8: Add a method to check if device supports faceID or not.
public func faceIDAvailable() -> Bool {
if #available(iOS 11.0, *) {
let context = LAContext()
return (context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil) && context.biometryType == .faceID)
}
return false
}
Step 9: Add a method to show authentication message regarding faceID or TouchId. If device supports faceID, it will pick the value from BioMetrixFaceIDError Enum otherwise from BioMetrixTouchIDErrors Enum.
func defaultBioMetrixAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdAuthenticationReason.rawValue
}
Step 10. Now Add a method to handle the error message, when user has too many failed attempts for faceID or TouchId.
func defaultPasscodeAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
}
Step 11. Now Add method to evaluate the specified policy for faceID or TouchID. More info: https://developer.apple.com/documentation/localauthentication/lacontext
func evaluate(policy: LAPolicy, with context: LAContext, reason: String, success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
context.evaluatePolicy(policy, localizedReason: reason) { (success, err) in
DispatchQueue.main.async {
if success { successBlock() }
else {
let errorType = AuthenticationError.initWithError(err as! LAError)
failureBlock(errorType)
}
}
}
}
Step 12: Add AuthenticationError enum to manage errors.
public enum AuthenticationError {
case failed, canceledByUser, fallback, canceledBySystem, passcodeNotSet, biometryNotAvailable, biometryNotEnrolled, biometryLockedout, other
public static func initWithError(_ error: LAError) -> AuthenticationError {
switch Int32(error.errorCode) {
case kLAErrorAuthenticationFailed:
return failed
case kLAErrorUserCancel:
return canceledByUser
case kLAErrorUserFallback:
return fallback
case kLAErrorSystemCancel:
return canceledBySystem
case kLAErrorPasscodeNotSet:
return passcodeNotSet
case kLAErrorBiometryNotAvailable:
return biometryNotAvailable
case kLAErrorBiometryNotEnrolled:
return biometryNotEnrolled
case kLAErrorBiometryLockout:
return biometryLockedout
default:
return other
}
}
// get error message based on type
public func message() -> String {
let authentication = BioMetrixAuthentication.shared
switch self {
case .canceledByUser, .fallback, .canceledBySystem:
return ""
case .passcodeNotSet:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kSetPasscodeToUseFaceID.rawValue : BioMetrixTouchIDErrors.kSetPasscodeToUseTouchID.rawValue
case .biometryNotAvailable:
return kBiometryNotAvailableReason
case .biometryNotEnrolled:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kNoFaceIdentityEnrolled.rawValue : BioMetrixTouchIDErrors.kNoFingerprintEnrolled.rawValue
case .biometryLockedout:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
default:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kDefaultFaceIDAuthenticationFailedReason.rawValue : BioMetrixTouchIDErrors.kDefaultTouchIDAuthenticationFailedReason.rawValue
}
}
}
If you followed all the steps mentioned above, your BioMetrixFile.swift will look like this.
import UIKit
import LocalAuthentication
public typealias AuthenticationSuccess = (() -> ())
public typealias AuthenticationFailure = ((AuthenticationError) -> ())
let kBiometryNotAvailableReason = "BioMetrix authentication is not available for this device."
enum BioMetrixTouchIDErrors: String {
//Touch ID
case kTouchIdAuthenticationReason = "Confirm your fingerprint to authenticate."
case kTouchIdPasscodeAuthenticationReason = "Touch ID is locked now, because of too many failed attempts. Enter passcode to unlock Touch ID."
/// Error Messages Touch ID
case kSetPasscodeToUseTouchID = "Please set device passcode to use Touch ID for authentication."
case kNoFingerprintEnrolled = "There are no fingerprints enrolled in the device. Please go to Device Settings -> Touch ID & Passcode and enroll your fingerprints."
case kDefaultTouchIDAuthenticationFailedReason = "Touch ID does not recognise your fingerprint. Please try again with your enrolled fingerprint."
}
enum BioMetrixFaceIDErrors: String{
//Face ID
case kFaceIdAuthenticationReason = "Confirm your face to authenticate."
case kFaceIdPasscodeAuthenticationReason = "Face ID is locked now, because of too many failed attempts. Enter passcode to unlock Face ID."
// Error Messages Face ID
case kSetPasscodeToUseFaceID = "Please set device passcode to use Face ID for authentication."
case kNoFaceIdentityEnrolled = "There is no face enrolled in the device. Please go to Device Settings -> Face ID & Passcode and enroll your face."
case kDefaultFaceIDAuthenticationFailedReason = "Face ID does not recognise your face. Please try again with your enrolled face."
}
open class BioMetrixAuthentication: NSObject {
// MARK: - Singleton
public static let shared = BioMetrixAuthentication()
}
// MARK:- Public
public extension BioMetrixAuthentication {
/// checks if TouchID or FaceID is available on the device.
class func canAuthenticate() -> Bool {
var isBioMetrixAuthenticationAvailable = false
var error: NSError? = nil
if LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
isBioMetrixAuthenticationAvailable = (error == nil)
}
return isBioMetrixAuthenticationAvailable
}
/// Check for BioMetrix authentication
class func authenticateWithBioMetrixs(reason: String, fallbackTitle: String? = "", cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultBioMetrixAuthenticationReason() : reason
let context = LAContext()
context.localizedFallbackTitle = fallbackTitle
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
/// Check for device passcode authentication
class func authenticateWithPasscode(reason: String, cancelTitle: String? = "", success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
let reasonString = reason.isEmpty ? BioMetrixAuthentication.shared.defaultPasscodeAuthenticationReason() : reason
let context = LAContext()
// cancel button title
if #available(iOS 10.0, *) {
context.localizedCancelTitle = cancelTitle
}
// authenticate
if #available(iOS 9.0, *) {
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthentication, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
} else {
// Fallback on earlier versions
BioMetrixAuthentication.shared.evaluate(policy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, with: context, reason: reasonString, success: successBlock, failure: failureBlock)
}
}
/// checks if face id is avaiable on device
public func faceIDAvailable() -> Bool {
if #available(iOS 11.0, *) {
let context = LAContext()
return (context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil) && context.biometryType == .faceID)
}
return false
}
}
// MARK:- Private
extension BioMetrixAuthentication {
/// get authentication reason to show while authentication
func defaultBioMetrixAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdAuthenticationReason.rawValue
}
/// get passcode authentication reason to show while entering device passcode after multiple failed attempts.
func defaultPasscodeAuthenticationReason() -> String {
return faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
}
/// evaluate policy
func evaluate(policy: LAPolicy, with context: LAContext, reason: String, success successBlock:@escaping AuthenticationSuccess, failure failureBlock:@escaping AuthenticationFailure) {
context.evaluatePolicy(policy, localizedReason: reason) { (success, err) in
DispatchQueue.main.async {
if success { successBlock() }
else {
let errorType = AuthenticationError.initWithError(err as! LAError)
failureBlock(errorType)
}
}
}
}
}
//----- Validations Errors // Success ------
public enum AuthenticationError {
case failed, canceledByUser, fallback, canceledBySystem, passcodeNotSet, biometryNotAvailable, biometryNotEnrolled, biometryLockedout, other
public static func initWithError(_ error: LAError) -> AuthenticationError {
switch Int32(error.errorCode) {
case kLAErrorAuthenticationFailed:
return failed
case kLAErrorUserCancel:
return canceledByUser
case kLAErrorUserFallback:
return fallback
case kLAErrorSystemCancel:
return canceledBySystem
case kLAErrorPasscodeNotSet:
return passcodeNotSet
case kLAErrorBiometryNotAvailable:
return biometryNotAvailable
case kLAErrorBiometryNotEnrolled:
return biometryNotEnrolled
case kLAErrorBiometryLockout:
return biometryLockedout
default:
return other
}
}
// get error message based on type
public func message() -> String {
let authentication = BioMetrixAuthentication.shared
switch self {
case .canceledByUser, .fallback, .canceledBySystem:
return ""
case .passcodeNotSet:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kSetPasscodeToUseFaceID.rawValue : BioMetrixTouchIDErrors.kSetPasscodeToUseTouchID.rawValue
case .biometryNotAvailable:
return kBiometryNotAvailableReason
case .biometryNotEnrolled:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kNoFaceIdentityEnrolled.rawValue : BioMetrixTouchIDErrors.kNoFingerprintEnrolled.rawValue
case .biometryLockedout:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kFaceIdPasscodeAuthenticationReason.rawValue : BioMetrixTouchIDErrors.kTouchIdPasscodeAuthenticationReason.rawValue
default:
return authentication.faceIDAvailable() ? BioMetrixFaceIDErrors.kDefaultFaceIDAuthenticationFailedReason.rawValue : BioMetrixTouchIDErrors.kDefaultTouchIDAuthenticationFailedReason.rawValue
}
}
}
Now moving to the UIViewController, where you want to integrate the FaceID or TouchID. Create UIButton IBAction method and follow below proper steps to manage the correct flow of authentication. Adding below lines of code in IBAction method of this button.
Steps 13: Starting Authentication by calling the class function of BioMetrixAuthentication Class in BioMetrixFile.swift file.
// start authentication
BioMetrixAuthentication.authenticateWithBioMetrixs(reason: "", success: {
// authentication successful
self.showLoginSucessAlert()
}, failure: { [weak self] (error) in
// do nothing on canceled
if error == .canceledByUser || error == .canceledBySystem {
return
}
// device does not support biometric (face id or touch id) authentication
else if error == .biometryNotAvailable {
print("Show Error: ", error.message())
}
// show alternatives on fallback button clicked
else if error == .fallback {
// here we're entering username and password
print("Normal login with username & password, make textfield first responder")
}
// No biometry enrolled in this device, ask user to register fingerprint or face
else if error == .biometryNotEnrolled {
self?.showGotoSettingsAlert(message: error.message())
}
// Biometry is locked out now, because there were too many failed attempts.
// Need to enter device passcode to unlock.
else if error == .biometryLockedout {
self?.showPasscodeAuthentication(message: error.message())
}
// show error on authentication failed
else {
print("Show Error: ", error.message())
}
})
Step 14: Add method to show passcode authentication
// show passcode authentication
func showPasscodeAuthentication(message: String) {
BioMetrixAuthentication.authenticateWithPasscode(reason: message, success: {
// passcode authentication success
self.showLoginSucessAlert()
}) { (error) in
print("Show Error: ", error.message())
}
}
Step 15: Adding a common method for login success.
func showLoginSucessAlert(){
print("Login Success")
}
Step 16: Handling the situation, when user has not enabled FaceId or TouchId.
func showGotoSettingsAlert(message: String){
let alert = UIAlertController(title: "Go to settings", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (self) in
let url = URL(string: "App-Prefs:root=TOUCHID_PASSCODE")
if UIApplication.shared.canOpenURL(url!) {
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
I would love to hear from you, if you have any doubt regarding code please leave comment below.
Comments
Post a Comment
Thank You.