FaceID authentication never fails through “authenticationFailed” policy error on real iPhoneX

Running the code below (a simple IBAction triggered by a button in a ViewController) in my real iPhoneX device, I'm not able to fall into evaluatePolicy's error case: LAError.authenticationFailed by failing FaceID recognition more times. This case is managed in the code by the function evaluatePolicyErrorMessage().


Using the Simulator instead, I can fall in LAError.authenticationFailed selecting 3 times the Hardware -> FaceID -> Non-matching Face, after the first "Try FaceID again" alert but WITHOUT tapping on the "Try FaceID again" button on the alert itself: just repeating 3 times the selection of Hardware -> FaceID -> Non-matching Face.


Could anybody suggest me what's going wrong in my code ? I'm sorry if it's quite prolix, but I tried to match all the possible cases and sub cases. I'm attaching a github link to this project: https://github.com/giurobrossi/testBiometricAuthentication.git




class ViewController: UIViewController {

  override func viewDidLoad() {
  super.viewDidLoad()
  }

  @IBAction func authButton(_ sender: Any) {

  let context = LAContext()

  if #available(iOS 10, *) {
  context.localizedCancelTitle = "Cancel"
  }

  var canEvaluateError: NSError?
  var message = ""

  if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &canEvaluateError) {
  if #available(iOS 11.0, *) {
  switch context.biometryType.rawValue {
  case 0:
  message = "Device doesn't have a biometry available"
  case 1:
  message = "Device has got TouchID"
  case 2:
  message = "Device has got FaceID"
  default:
  message = "biometry device not recognized"
  }
  } else {
  message = "Device is eligible to evaluate the authentication. (iOS < 11)"
  }
  //self.presentAlertMainThread(message: message)
  print(message)
  context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Let's authenticate!" ) { success, evaluateError in
  if success {
  self.presentAlertMainThread(message: "Login succesful !")
  }
  else {
  message = self.evaluatePolicyErrorMessage(errorCode: (evaluateError! as NSError).code)
  self.presentAlertMainThread(message: message )
  print("EvaluatePolicy Error: \(evaluateError!._code)")
  }
  }
  }
  else {
  message = canEvaluatePolicyErrorMessage(errorCode: (canEvaluateError?.code)!, context: context)
  self.presentAlertMainThread(message: message)
  print("CanEvaluatePolicy Error: \(String(describing: canEvaluateError?.code ))")
  }
  }
}


extension ViewController {

  func presentAlertMainThread(message: String) {
  DispatchQueue.main.async(execute: {
  let alertController = UIAlertController(title: "Attenzione", message: message, preferredStyle: UIAlertControllerStyle.alert)
  self.present(alertController, animated: true, completion: nil)
  let defaultAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)
  alertController.addAction(defaultAction)
  })
  }

  func canEvaluatePolicyErrorMessage(errorCode: Int, context: LAContext) -> String {
  var message = ""
  if #available(iOS 11.0, *) {
  switch errorCode {
  // -6
  case LAError.biometryNotAvailable.rawValue:
  switch context.biometryType.rawValue {
  case 0: // non capita mail (iOS >= 11)
  message = "Device does not have any biometry available"
  case 1:
  message = "Device has got TouchID but the permission is false"
  case 2:
  message = "Device has got FaceID but the permission is false"
  default:
  message = "biometry sensor not recognized"
  }
  // TBD
  case LAError.biometryLockout.rawValue:
  switch context.biometryType.rawValue {
  case 0:
  message = "Device does not have any biometry available"
  case 1:
  message = "Device has got TouchID but the biometry is locked"
  case 2:
  message = "Device has got FaceID but the biometry is locked"
  default:
  message = "biometry sensor not recognized"
  }
  // -7
  case LAError.biometryNotEnrolled.rawValue:
  switch context.biometryType.rawValue {
  case 0:
  message = "Device does not have any biometry available"
  case 1:
  message = "Device has got TouchID but the biometry is not enrolled"
  case 2:
  message = "Device has got FaceID but the biometry is not enrolled"
  default:
  message = "biometry sensor not recognized"
  }
  default:
  message = "Policy Error type not managed."
  }
  }
  else if #available(iOS 9.0, *) {
  switch errorCode {
  // TBD
  case LAError.touchIDLockout.rawValue:
  if #available(iOS 11.0, *) {
  switch context.biometryType.rawValue {
  case 0:
  message = "Device does not have any biometry available"
  case 1:
  message = "Device has got TouchID but the biometry is locked"
  case 2:
  message = "Device has got FaceID but the biometry is locked"
  default:
  message = "biometry sensor not recognized"
  }
  } else {
  message = "Too much auth. attempts by TouchID (9.0 <= iOS < 11.0) - Auth. Blocked"
  }
  // -6
  case LAError.touchIDNotAvailable.rawValue:
  if #available(iOS 11.0, *) {
  switch context.biometryType.rawValue {
  case 0:
  message = "Device does not have any biometry available"
  case 1:
  message = "Device has got TouchID but the permission is false"
  case 2:
  message = "Device has got FaceID but the permission is false"
  default:
  message = "biometry sensor not recognized"
  }
  } else {
  message = "Device does not have TouchID available (9.0 <= iOS < 11.0)"
  }
  // -7
  case LAError.touchIDNotEnrolled.rawValue:
  if #available(iOS 11.0, *) {
  switch context.biometryType.rawValue {
  case 0:
  message = "Device does not have any biometry available"
  case 1:
  message = "Device has got TouchID but the biometry is not enrolled"
  case 2:
  message = "Device has got FaceID but the biometry is not enrolled"
  default:
  message = "biometry sensor not recognized"
  }
  } else {
  message = "TouchID not 'enrolled' (9.0 <= iOS < 11.0)"
  }
  default:
  message = "Policy Error type not managed."
  }
  }
  else {
  // 8.0 <= iOS < 9.0
  print("canEvaluatePolicy Error: \(errorCode.description)")
  }
  return message;
  }


  func evaluatePolicyErrorMessage(errorCode: Int) -> String {
  var message = ""
  if #available(iOS 9.0, *) {
  switch errorCode {
  // -1
  case LAError.authenticationFailed.rawValue:
  message = "Authentication Failed"
  // -9
  case LAError.appCancel.rawValue:
  message = "Authentication cancelled by the app"
  // TBD
  case LAError.invalidContext.rawValue:
  message = "LAContext passed to this call has been previously invalidated"
  // TBD
  case LAError.notInteractive.rawValue:
  message = "Authentication failed, because it would require showing UI which has been forbidden by using interactionNotAllowed property"
  // TBD
  case LAError.passcodeNotSet.rawValue:
  message = "Passcode not set"
  // -4
  case LAError.systemCancel.rawValue:
  message = "Authentication cancelled by iOS"
  // -2
  case LAError.userCancel.rawValue:
  message = "Authentication cancelled by the user"
  // -3 
  case LAError.userFallback.rawValue:
  message = "Fallback selected by the user
  default:
  message = "Error not managed"
  }
  } else {
  message = "Failed Authentication (8.0 <= iOS < 9.0) - Code: \(errorCode.description)"
  }
  return message
  }
}
Post not yet marked as solved Up vote post of giurobrossi69 Down vote post of giurobrossi69
2.4k views