Implement Face ID Authentication in the iOS App

Enhance your iOS app's security and user experience with FaceID
Apr 26 2024 · 8 min read

Introduction

Biometric authentication has transformed mobile security, and Apple’s Face ID, introduced with the iPhone X in 2017, stands at the forefront of this revolution.

Utilizing advanced facial recognition technology, Face ID goes beyond traditional methods by leveraging depth-sensing cameras and machine learning algorithms.

Stored securely within the device’s Secure Enclave, Face ID creates a mathematical representation of facial features, ensuring unparalleled security. Moreover, its seamless integration into iOS apps has redefined user interactions, setting new standards for mobile authentication.

In this blog, we’ll delve into the process of integrating Face ID into iOS apps to enhance both security and user experience.

We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!

Understanding Face ID

Face ID replaces the conventional Touch ID with a sophisticated facial recognition system, providing users with a secure and effortless means of unlocking their devices and validating actions.

Utilizing the TrueDepth camera system, Face ID captures and analyzes facial features in real-time, generating a precise 3D map. This map, created from thousands of infrared dots projected onto the face, forms a unique Faceprint and is securely stored within the device’s Secure Enclave.

During authentication, Face ID compares the current facial data with the stored Faceprint, utilizing machine learning to adapt to changes in appearance and ensure consistent performance.

In essence, Face ID represents a user-friendly authentication solution that integrates cutting-edge technology to redefine mobile security and user experience standards on iOS devices.

1. Face ID’s Advantages

Face ID presents a significant leap forward in security and convenience compared to traditional password or PIN-based authentication methods. 

By relying on facial recognition technology, it eliminates the need for manual input, offering users a seamless and hands-free authentication experience.

Furthermore, the inherent uniqueness of facial features makes Face ID exceptionally secure, greatly reducing the risk of phishing attacks.

2. Addressing Concerns

Misconceptions surrounding Face ID’s security and privacy have led to concerns about data storage and privacy risks. However, it’s crucial to understand that Face ID securely stores facial data locally on the device, making it inaccessible to apps or cloud services.

Additionally, it continually improves its performance through machine learning algorithms, enhancing security measures while safeguarding user privacy.

Since Face ID operates solely on the device, it doesn’t transmit facial data externally, mitigating privacy concerns and ensuring user data remains protected.

Requirements & Prerequisites

To successfully integrate Face ID into iOS apps, developers need to meet specific hardware, software, and prerequisite criteria.

1. Hardware Requirements

Ensure the device includes a TrueDepth camera system (a key component for Face ID functionality) available on iPhone X and later models.

2. Software Requirements

Face ID integration requires a minimum iOS version compatible with the TrueDepth camera, typically iOS 11 or later.

3. Prerequisites

  1. Privacy Considerations: Developers should adhere to Apple's privacy guidelines and obtain user consent when accessing biometric data for authentication.
  2. App Capabilities: Ensure that the app's Info.plist file includes the necessary permissions and descriptions for accessing FaceID usage description. This is crucial for informing users about why the app requires Face ID access.

Integration Steps

To access the faceID into an iOS app involves several key steps:

Update Info.plist: Add ‘Privacy — Face ID Usage Description’ to the app’s Info.plist file. This string value informs users about the app’s use of Face ID and should be concise yet informative.
Note: Keep the key as sort as possible which allow user to undestand the purpose of permission.

Create Authenticator: Create an authenticator within the app that uses Face ID APIs to authenticate users. Import the ‘LocalAuthentication module, which provides access to authentication-related APIs.
Note: The app’s Info.plist must contain an NSFaceIDUsageDescription key with a string value explaining to the user how the app uses this data.

1. Availability Check

Here we need to check whether the device supports biometric authentication or not, to do that we have created an extension of LAContext which provides the status of availability.

extension LAContext {
    enum BiometricType: String {
        case none
        case touchID
        case faceID
        case opticID // Added a case for potential future biometric methods
    }

    // Returns the detected biometric type based on the capabilities of the device
    var biometricType: BiometricType {
        var error: NSError?

        // Check if the device supports biometric authentication
        guard self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            return .none
        }

        if #available(iOS 11.0, *) {
            // Determine the specific biometric type supported on iOS 11.0 and later
            switch self.biometryType {
            case .none:
                return .none
            case .touchID:
                return .touchID
            case .faceID:
                return .faceID
            default:
                if #available(iOS 17.0, *) {
                    // Support for potential future biometric methods
                    if self.biometryType == .opticID {
                        return .opticID
                    } else {
                        return .none
                    }
                }
            }
        }

        // Fallback to checking for Touch ID support on iOS versions older than 11.0
        return self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touchID : .none
    }
}

This extension introduces an enum BiometricType, enabling developers to determine the supported biometric authentication method on iOS devices. 

It checks for the device supports for the biometric authentication using canEvaluatePolicy(_:error:). If biometric authentication is not available, it returns .none.

For devices running iOS 11.0 and later, it utilizes the biometryType property of LAContext to determine the supported biometric type.

Additionally, it includes a case for potential future biometric methods, such as optic scanning, and provides a fallback mechanism for devices running iOS versions older than 11.0.

2. Implementing authentication

For authentication, we have created an Authenticator class which manages all the authentication-related things.

In this class, we have the authenticate() method to authenticate whether the user is verified or not and it will act accordingly.

private func authenticate() {

    // Check if biometric authentication is available and not locked
    guard isBiometricAvailable() && !isBiometricLocked else { return }

    isLoading = true
    var error: NSError?
    
    // Check if biometric authentication is possible
    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {

        // Perform biometric authentication
        let reason = "We need to unlock your data."
        
        // It presents a localized reason for authentication to the user, explaining why authentication is necessary.
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { [weak self]
            success, authenticationError in
            guard let self else { return }

            // Handle authentication completion
            if success {
                // proceeds to decrypt and verify the user's passcode
                let passcode = self.decryptUserPasscode()
                self.isAuthenticated = passcode != nil
            } else {
                // increments the failedAttempt count
                self.failedAttempt += 1

                // check for the maximum allowed failed limit
                self.isBiometricLocked = self.failedAttempt >= self.maxFailedAttemptAllowed
            }
            self.isLoading = false
        }
    } else {
        // Handle error if biometric authentication is not possible
        if let error {
            handleLaError(error: error)
        } else {
            // Handle other errors if any
        }
    }
}

This authenticate() function is responsible for initiating the biometric authentication process.

  • It verifies the availability and unlock status of biometric authentication using canEvaluatePolicy. If it's possible, it proceeds with it and handles success or failure accordingly.
  • If it succeeds move ahead otherwise we check for the failed limit, if it reaches the maximum limit then basically we disable the biometric authentication temporarily.
  • If biometric authentication is unavailable, it checks for the error. If an error is present, it handles the error appropriately.

This function provides a structured approach to biometric authentication, handling success, failure, and error scenarios gracefully while ensuring a smooth user experience.

  1. If you ask for the deviceOwnerAuthenticationWithBiometrics policy then you will have to handle the fallback yourself.
  2. If you ask for deviceOwnerAuthentication only, then biometrics will be used if available and authorized, otherwise, it will automatically fall back to passcode if biometrics is unavailable, or give you the fallback option to enter passcode automatically if biometrics attempt fails.

3. Secure Passcode

func setPasscodeWith(_ code: String) {
    // Check if biometric authentication is available
    guard isBiometricAvailable() else { return }

    // Generate a unique encryption key
    let key = UUID().uuidString // Instead of this random string you can use a key that can be recoverable

    // Encrypt the passcode using AES encryption algorithm
    let encryptedPasscode = AESEncryptionManager.encrypt(plainText: code, key: key)

    // Store the encrypted passcode and encryption key securely
    userDefault.setValue(encryptedPasscode, forKey: userDefaultPasscodeKey)
    userDefault.setValue(key, forKey: userDefaultSecretKey)
}

This Swift function setPasscodeWith(_:) is responsible for setting a passcode using biometric authentication, if available.

Here's a breakdown of its functionality:

  1. It checks for biometric authentication availability.
  2. It generates a unique encryption key using UUID().uuidString that will be used to encrypt the passcode securely.
  3. It encrypts the provided passcode using the generated encryption key. The AESEncryptionManager.encrypt(plainText:key:) function encrypts the passcode using the AES encryption algorithm.
  4. It stores the encrypted passcode and the encryption key securely in the user defaults. 

The encrypted passcode is stored with a key userDefaultPasscodeKey and the encryption key is stored with a key userDefaultSecretKey, This ensures that the passcode remains stored on the device.

If you are not aware about AES encryption then here is the good Article on it which explains AES encryption in detail, written by Amisha.

Handle Fallback

In this post, we have used deviceOwnerAuthenticationWithBiometrics policy so we need to handle fallback if the failed attempt reaches its maximum limit.

In the example, we have managed it with failedAttempt a variable that records the failed attempt.

We’ve kept the limit to 3 failed attempts, you can choose according to your convenience.

Once the limit reaches the max we hide the FaceID option and force the user to enter the passcode.

And then we match the entered passcode with the stored passcode and authenticate the user based on that.

// Decrypt stored passcode
func decryptUserPasscode() -> String? {
    guard let encryptedPasscode = userDefault.value(forKey: userDefaultPasscodeKey) as? String else { return nil }
    var passcode: String?
    if let key = userDefault.value(forKey: userDefaultSecretKey) as? String {
        passcode = AESEncryptionManager.decrypt(encryptedText: encryptedPasscode, key: key)
        isAuthenticated = passcode != nil
    }
    return passcode
}

// Verify entered passcode
func verifyPin(pin: String) {
    guard let passcode = decryptUserPasscode() else {
        isAuthenticated = false
        return
    }
    isAuthenticated = passcode == pin
    resetFailCount()
}

Here is the breakdown of decryptUserPasscode(),

  1. It checks whether a passcode is set or not.
  2. If set then it will return the passcode.

Now verifyPin(pin: _) does the rest of the work done, 

  1. It will get the passcode with the help of decryptUserPasscode().
  2. Once the passcode is received it will compare it with the argument pin.
  3. The result of this comparison is set to isAuthenticated.

Testing and Debugging

For testing, initiate the app and utilize the FaceID button located at the bottom. In a real application, you can directly call the authenticate() method on viewAppear or in view’s .task() modifier callback to make authentication smoother and faster.

When the FaceID recognition pop-over appears, press Cmd+Opt+M to trigger the matching face action. You can locate this option in the simulator’s Menu -> Features -> Face ID -> Matching Face for testing matching faces.

To test non-matching faces, press Cmd+Opt+N or access the simulator’s Menu -> Features -> Face ID -> Non-matching Face option.

Make sure you have enabled Enrolled from Manu->Face ID->Enrolled otherwise you will be not able to test FaceID in the simulator.

By following the above steps you can successfully test the FaceID behavior in the simulator.

That’s it for today.

The full source code is available in this GitHub Repo.

Conclusion

In conclusion, integrating biometric authentication, particularly Face ID, into iOS apps represents a substantial leap forward in both the security and user experience realms.

With its superior security features and seamless user experience, Face ID surpasses traditional authentication methods like passwords.

By embracing biometric authentication, developers play a pivotal role in cultivating a more secure and user-friendly digital landscape, providing iOS users with smooth and dependable authentication experiences.

Are you looking for a similar implementation on the Android side?
Here is the Article written by Megh.

Happy coding… 🤖


divyesh-v image
Divyesh Vekariya
Divyesh is an iOS developer with 2+ years of experience. He is sharing knowledge of SwiftUI and Other iOS technologies.


divyesh-v image
Divyesh Vekariya
Divyesh is an iOS developer with 2+ years of experience. He is sharing knowledge of SwiftUI and Other iOS technologies.

canopas-logo
We build products that customers can't help but love!
Get in touch

Let's Work Together

Not sure where to start? We also offer code and architecture reviews, strategic planning, and more.

cta-image
Get Free Consultation
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.