Skip to main content

Integrate the FaceID or TouchID Authentication in iOS Apps - Swift

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.

Comments

Popular Posts

How I Reduced the Size of My React Native App by 85%

How and Why You Should Do It I borrowed 25$ from my friend to start a Play Store Developer account to put up my first app. I had already created the app, created the assets and published it in the store. Nobody wants to download a todo list app that costs 25mb of bandwidth and another 25 MB of storage space. So today I am going to share with you how I reduced the size of Tet from 25 MB to around 3.5 MB. Size Matters Like any beginner, I wrote my app using Expo, the awesome React Native platform that makes creating native apps a breeze. There is no native setup, you write javascript and Expo builds the binaries for you. I love everything about Expo except the size of the binaries. Each binary weighs around 25 MB regardless of your app. So the first thing I did was to migrate my existing Expo app to React Native. Migrating to React Native react-native init  a new project with the same name Copy the  source  files over from Expo project Install all de...

How to recover data of your Android KeyStore?

These methods can save you by recovering Key Alias and Key Password and KeyStore Password. This dialog becomes trouble to you? You should always keep the keystore file safe as you will not be able to update your previously uploaded APKs on PlayStore. It always need same keystore file for every version releases. But it’s even worse when you have KeyStore file and you forget any credentials shown in above box. But Good thing is you can recover them with certain tricks [Yes, there are always ways]. So let’s get straight to those ways. 1. Check your log files → For  windows  users, Go to windows file explorer C://Users/your PC name/.AndroidStudio1.4 ( your android studio version )\system\log\idea.log.1 ( or any old log number ) Open your log file in Notepad++ or Any text editor, and search for: android.injected.signing and if you are lucky enough then you will start seeing these. Pandroid.injected.signing.store.file = This is  file path where t...

Video Calling In IOS Objective C

Video Calling Sources Project homepage on GIT — https://github.com/QuickBlox/quickblox-ios-sdk/tree/master/sample-videochat-webrtc Download ZIP - https://github.com/QuickBlox/quickblox-ios-sdk/archive/master.zip Overview The VideoChat code sample allows you to easily add video calling and audio calling features into your iOS app. Enable a video call function similar to FaceTime or Skype using this code sample as a basis. It is built on the top of WebRTC technology.            System requirements The QuickbloxWebRTC.framework supports the next:     * Quickblox.framework v2.7 (pod QuickBlox)     * iPhone 4S+.     * iPad 2+.     * iPod Touch 5+.     * iOS 8+.     * iOS simulator 32/64 bit (audio might not work on simulators).     * Wi-Fi and 4G/LTE connections. Getting Started with Video Calling API Installation with CocoaPods CocoaPods is a dependency manag...