How to Detect a Change in Biometrics on iOS

If you are developing an app that deals with sensitive user information, such as financial or health, you may want to detect if a user added/removed a fingerprint from TouchID, or added/removed a face from FaceID. This can prevent a situation where someone gets ahold of the user’s device, then adds their own biometrics identification in order to get into the app.

Since iOS 9, The LAContext class in the LocalAuthentication framework provides a way to check for this type of change.

There is a property on an LAContext instance named evaluatedPolicyDomainState, which tells us the current state of the policy that was evaluated. In terms of biometrics, it’s a piece of data that represents what face(s) or fingerprint(s) are currently enrolled. So, comparing the current state to an earlier saved state will tell us whether something has changed on what identification has been enrolled.

What we’ll do is add an extension to LAContext that provides way to store the state data in User Defaults, and a method to check if the value has changed.

extension LAContext {
    static var savedBiometricsPolicyState: Data? {
        get {
            UserDefaults.standard.data(forKey: "BiometricsPolicyState")
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "BiometricsPolicyState")
        }
    }
    
    static func biometricsChanged() -> Bool {
        let context = LAContext()
        var error: NSError?
        context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
        
        // If there is no saved policy state yet, save it
        if error == nil && LAContext.savedBiometricsPolicyState == nil {
            LAContext.savedBiometricsPolicyState = context.evaluatedPolicyDomainState
            return false
        }
        
        if let domainState = context.evaluatedPolicyDomainState, domainState != LAContext.savedBiometricsPolicyState {
            return true
        }
        
        return false
    }
}

We can now use this function to determine if biometrics enrollment has changed whenever a user logs in.

func handleLogIn() {
    let context = LAContext()
    // Check first that biometrics is available
    guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) else {
        // Handle not being able to use biometrics
        return
    }
    
    guard !LAContext.biometricsChanged() else {
        // Handle biometrics changed, such as clearing credentials and making the user manually log in
        // Reset LAContext.savedBiometricsPolicyState to nil after doing so
        return
    }
    
    // Log in with biometrics
    let reason = "Sign in to your account"
    context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason ) { success, error in

        if success {
            LAContext.savedBiometricsPolicyState = context.evaluatedPolicyDomainState
            
            DispatchQueue.main.async {
                // Go to next screen after login success
            }

        } else {
            // Handle error
        }
    }
}