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.
FaceID/TouchID
then you should simply show your default authentication flow. On the device, check that you have enabledFaceID/TouchID
. It works on Simulator because you can simply enroll and match/unmatch. – Dermatoglyphicspolicy
has to be changed toLAPolicy.deviceOwnerAuthentication
so that it falls back to passcode auth. – Conall