Skip to content

Biometric Authentication in SwiftUI

Posted on:January 16, 2023

Table of contents

Open Table of contents

Introduction

Facial recognition and fingerprint technology, such as FaceID and TouchID, has become increasingly popular in modern mobile devices. In this tutorial, we will show you how to use the FaceID and TouchID functionality in your SwiftUI applications to provide increased security and convenience for your users.

Basic Functionality

import LocalAuthentication
import Foundation

class LocalAuthenticationService {
    class func authenticateWithBiometrics(_ completion: @escaping (Result<Void, Error>) -> Void) {
        let context = LAContext()
        var error: NSError?

        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            let reason = "We need to unlock your data."

            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                if success {
                    completion(.success(()))
                } else {
                    completion(.failure(AppError.error(error?.localizedDescription ?? "Error Undefined")))
                }
            }
        } else {
            completion(.failure(AppError.error("No Biometrics Available")))
        }
    }
}

This code presents a Swift class called LocalAuthenticationService that provides an easy way to use the biometric authentication functionality in your application. The static method authenticateWithBiometrics is the one that provides the authentication functionality. This method takes a callback function as a parameter that is executed when the authentication is completed.

The authenticateWithBiometrics function creates an instance of LAContext, which is an iOS class that provides access to the biometric authentication functionality. It checks if the device is compatible with biometric authentication and if the user has set up a fingerprint or face to use as a security measure. If it is compatible, the evaluatePolicy method is called on the context with the argument .deviceOwnerAuthenticationWithBiometrics. This method displays a dialog for the user to authenticate using their fingerprint or face. The localizedReason argument is a string that is displayed in the dialog to indicate to the user the reason for the authentication request.

The parameter of evaluatePolicy is a callback block that is called when the user has authenticated. If the user has authenticated successfully, the callback function is called with a Success Result. If it does not authenticate successfully, the callback function is called with an Error Result. The error result may be the error returned in case of authentication failure or “No Biometrics Available” if the device does not support biometric authentication.

Before continuing we have to add the NSFaceIDUsageDescription key to our Info.plist, NSFaceIDUsageDescription is an Info.plist key that is used to describe the purpose of using facial recognition technology (Face ID) in the application. It is necessary to include this key in your project’s Info.plist file if your application uses Face ID for user authentication.

The reason for this is that, according to Apple’s development guidelines, all applications that use private system features, such as Face ID, must provide a clear and accurate description of the use of these features. This provides transparency for users about how their biometric information will be used, and allows them to make an informed decision.

LockScreen

Now, let’s see how we can use the authenticateWithBiometrics function with an example:

import SwiftUI

struct LockScreenView<Content: View>: View {
    // State variable to indicate if the view is unlocked or not
    @State private var isUnlocked = false
    // Access the current scene phase/state
    @Environment(\.scenePhase) var scenePhase

    // Content to be displayed
    let content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        Group {
            // If the view is unlocked, show the content
            if isUnlocked {
                content
            } else {
                // If not unlocked, show a biometric unlock view
                UnlockWithBiometricsView(action: authenticateWithBiometrics)
            }
        }
        // Listen to scene phase change
        .onChange(of: scenePhase) { newPhase in
            // If the phase changes to background, lock the content
            if newPhase == .background {
                isUnlocked = false
            }
        }
    }

    func authenticateWithBiometrics() {
        // Call the local authentication service to authenticate with biometrics
        LocalAuthenticationService.authenticateWithBiometrics { result in
            switch result {
            case .success():
                // If successful, unlock the view
                isUnlocked = true
            case .failure(let error):
                // If failed, print the error message
                print(error.localizedDescription)
            }
        }
    }
}

This code shows a SwiftUI structure called LockScreenView that can be used to display content in an application with a lock screen. The structure takes a generic parameter Content that must be a SwiftUI view.

The structure has a state isUnlocked that indicates if the content is unlocked or not. It also has a scenePhase property that is used to listen for the change in the application scene state.

The structure has an initializer that takes a view building function that is used to create the content to be displayed. In the body of the structure, the unlock view calls the authenticateWithBiometrics method to authenticate the user.

The authenticateWithBiometrics method calls the local authentication service to authenticate the user with biometrics. If the authentication is successful, the isUnlocked state is changed to true to unlock the content. If it fails, an error message is printed.

The body of the structure also has a listener for the change in scene state. If the state changes to background, the content is locked by changing the isUnlocked state to false. This is important if we want to protect the information after the application goes to the background.

Example

Finally, let’s see how easy it is to use the LockScreenView view:

import SwiftUI

struct ContentView: View {
    var body: some View {
            // Wrap the content in a LockScreenView
            LockScreenView {
                // Content
                VStack {
                    Image(systemName: "globe")
                        .imageScale(.large)
                        .foregroundColor(.accentColor)
                    Text("Hello, world!")
                }
                .padding()
            }
        }
}

Within the LockScreenView structure is the actual content of the application, in this case a VStack that contains an image and text.

Conclusion

To summarize, in this tutorial we have learned how to use biometric authentication functionality in a SwiftUI application. We have seen how to create a local authentication service class that uses LAContext to authenticate the user with their fingerprint or face. We have also seen how to use this service class in a custom lock screen view called LockScreenView to protect the application’s content. Finally, we have seen how to use LockScreenView to protect the application’s content in the main view.

I hope this tutorial has provided you with a better understanding of how to use biometric authentication functionality in your SwiftUI applications. Remember that this is just one way to implement this functionality, and there are many other ways to protect the content of your application. It is always important to evaluate the security needs of your application and choose the best solution for your specific use case.

References