FaceID should fallback to Passcode but does not
Asked Answered
C

1

6

I have inherited a code base with the following class providing support for Face/Touch ID.

The expected behaviour is that on Face/Touch ID success the user is signed in. This works.

However, should the user fail Face ID and opt to enter their passcode, they are signed out once the completion handler is called. I believe opting to use passcode is triggering

else {
 self.authState = .unauthenticated
 completion(.unauthenticated)
}

How can I trigger the passcode prompt instead? Should I create a second policy using LAPolicy.deviceOwnerAuthentication and evaluate that instead?

import LocalAuthentication

public enum AuthenticationState {
    case unknown
    case authenticated
    case unauthenticated

    public func isAuthenticated() -> Bool {
        return self == .authenticated
    }
}

public protocol TouchIDAuthenticatorType {
    var authState: AuthenticationState { get }
    func authenticate(reason: String, completion: @escaping (AuthenticationState) -> Void) -> Void
    func removeAuthentication() -> Void
}

public protocol LAContextType: class {
    func canEvaluatePolicy(_ policy: LAPolicy, error: NSErrorPointer) -> Bool
    func evaluatePolicy(_ policy: LAPolicy, localizedReason: String, reply: @escaping (Bool, Error?) -> Void)
}

public class TouchIDAuthenticator: TouchIDAuthenticatorType {
    public var authState: AuthenticationState = .unknown

    private var context: LAContextType
    private var policy = LAPolicy.deviceOwnerAuthenticationWithBiometrics

    public init(context: LAContextType = LAContext()) {
        self.context = context
    }

    public func authenticate(reason: String, completion: @escaping (AuthenticationState) -> Void) -> Void {
        var error: NSError?

        if context.canEvaluatePolicy(policy, error: &error) {
            context.evaluatePolicy(policy, localizedReason: reason) { (success, error) in
                DispatchQueue.main.async {
                    if success {
                        self.authState = .authenticated
                        completion(.authenticated)
                    } else {
                        self.authState = .unauthenticated
                        completion(.unauthenticated)
                    }
                }
            }
        } else {
            authState = .authenticated
            completion(.authenticated)
        }
    }

    public func removeAuthentication() -> Void {
        authState = .unknown
        context = LAContext() // reset the context
    }
}

extension LAContext: LAContextType { }

I should point out, on the simulator this appears to work as expected, but on a device it does not and I signed out.

Cavour answered 18/4, 2019 at 11:51 Comment(4)
Have you tried putting a breakpoint in and inspecting what the error is? You'll know exactly which part of your code is executing at least, since you don't sound 100% sure.Winstonwinstonn
If user has not enabled FaceID/TouchID then you should simply show your default authentication flow. On the device, check that you have enabled FaceID/TouchID. It works on Simulator because you can simply enroll and match/unmatch.Dermatoglyphics
Maybe this one Helps you Please check and try it https://mcmap.net/q/1055884/-face-id-evaluation-process-not-working-properlyGlinys
The policy has to be changed to LAPolicy.deviceOwnerAuthentication so that it falls back to passcode auth.Conall
S
19

You have to use .deviceOwnerAuthentication instead of asking for biometrics. If FaceID is available, it will force the attempt to use this either way.

If you try enough times then you will get another dialogue to "Cancel" or fallback to "Use passcode". Choosing the fallback will show you the passcode screen.

However, if you specified .deviceOwnerAuthenticationWithBiometrics, you will get the same fallback option. Rather than getting this dialogue I would have expected to receive an error of LAError.Code.biometryLockout. But instead I get this fallback option dialogue. But that is ok...

However, if I then tap the fallback option to "Use passcode", it will NOT present the passcode alert. Instead it fails with a LAError.Code.userFallback error.

If you use the policy without biometrics, you will not get and be able to catch the .userFallback error.

So to sum things up:

  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 attemps fail.
Seely answered 29/1, 2021 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.