LAContext evaluatePolicy does not always prompt user - ios

In my iOS 7 iPad app LAContext:evaluatePolicy sometimes returns SUCCESS without prompting the user to touch the ID button. And the Apple docs say “Evaluating a policy MAY involve prompting the user…”.
My authentication policy is set to LAPolicyDeviceOwnerAuthenticationWithBiometrics, the only choice I see. Why wouldn’t this prompt the user to touch the ID button every time I call evaluatePolicy? Is there a way I can require this behavior?

I have experienced a similar problem.
It is possible that you are declaring a global variable something like
let authenticationContext = LAContext()
and then use authenticationContext within your class methods and functions.
I have started declaring the constant in each function I use it like
func someAuthFunc() {
let authenticationContext = LAContext()
...
and my problem was solved.
I was asked each time I requested evaluateForContext ...
I hope this helps.
Cheers

For who have the same issue
It just happens from iOS 13 and above. The solution is trying to call evaluate function twice like this:
let systemVersion = UIDevice.current.systemVersion
// Trick here: Try to do a pre-evaluate
if systemVersion.compare("13.0", options: .numeric) != .orderedAscending {
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Authenticate to open the app", reply: { (_, _) in
//Ignore callback here
})
}
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Authenticate to open the app", reply: { (success, error) in
// Handle callback here
})
Tested and work well for all iOS 13.x.x versions so far.

I experience the same problem after iOS13 update. Not a good workaround but calling evaluatePolicy twice solved the problem for me
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { _, _ in
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { isSuccess, _ in
DispatchQueue.main.async {
if isSuccess {
success()
} else {
fail(authError?.localizedDescription ?? "User did not authenticate successfully")
}
}
}
}

This seems to be a bug on iOS 13, 13.1 and is planned to be fixed in 13.2. I'd suggest trying your code on the iOS 13.2 beta to see if it's any better.
source: iOS 13 Touch ID Delay/Bug

Related

Xcode simulator does not show Face ID biometrics prompt

`Hi everyone, I am new to Swift and following a biometrics authentication tutorial to click a button and use Face ID or Touch ID.
Nothing happens when the biometric authentication button is pressed because canEvaluate etc do not get set to true. Details below.
variables and the relevant function
private(set) var context = LAContext()
#Published private(set) var biometryType: LABiometryType = .none
private(set) var canEvaluatePolicy = false
#Published private(set) var isAuthenticated = false`
func getBiometryType(){
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
biometryType = context.biometryType
}
func authenticatedWithBiometrics() async {
context = LAContext()
print("inside auth func")
if canEvaluatePolicy{
let reason = "to log in to your account"
do{
let success = try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
print(success)
if success {
DispatchQueue.main.async {
self.isAuthenticated = true
print("is authenticated", self.isAuthenticated)
}
}
}
catch{ //if fail to authenticate, throw an error, show it, no biometry
print(error.localizedDescription)
DispatchQueue.main.async {
self.errorDescription = error.localizedDescription
self.showAlert = true
self.biometryType = .none
}
}
}
}
The issue: nothing happens when I click on the button that calls authenticatedwithbiometrics(). The function is called, the issue is that canEvaluatePolicy is FALSE and does not get set to true in getBiometryType. If I manually configure variables canEvaluatePolicy and success to true, we eventually run into the error.localizedDescription -> "biometry is not enrolled".
I've tried different simulators: iPhone 13, 14, 14 Pro, 14Pro Max, and SE which uses TouchID, and that also does nothing.
I've seen someone who had a same problem, but then it was solved by trying different simulators. That did not solve my issue.
I don't think it's an issue with the code because it's literally copied from a working tutorial. Do I need to configure something in the simulator?
Can we test Face ID in simulator? this type of answer is not useful to me because I cannot even evoke the gray popup that initiates the face or touch ID process.
I am using the newest version of xcode.
Thank you. `
I know why now. You have to restart the build after checking the "Enrolled face" button for the cue to show up.
You can used biometric authentication with simulator but you have to ensure you check enrolled in feature > FaceId > Enrolled after this you can check both scenarios Matching or Non Matching

Authenticating the user with biometrics causing application to crash

So i'm following the books in terms of authenticating a user using biometrics. Below is some code i've wrote in a custom class called biometrics manager.
func authenticateUser(completion: #escaping (_ result: BiometricsStatus) -> Void) {
DispatchQueue.main.async {
guard self.deviceHasBiometricCapabilities() else { completion(.fail(error: .touchIDNotAvailable)); return }
let authMethod = self.biometricType() == .faceID ? "Face ID" : "Touch ID"
let loginMessage = "\(authMethod) to sign in"
self.context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: loginMessage) { success, evaluateError in
if success {
completion(.success)
return
}
if let error = evaluateError {
completion(.fail(error: self.getBiometricsError(from: error)))
return
}
}
}
}
I've debugged my application and it seems to be causing a crash on the evaluate policy line, i've enabled exception breakpoints to try and catch the crash but i'm receiving nothing at all in the console logs. The only thing that i seem to be getting in the console is the following.
Message from debugger: Terminated due to signal 9
Which isn't super helpful any possible pointers or ideas that may be causing this crash to occur at all?
You need to add the NSFaceIDUsageDescription key to your info.plist
From https://developer.apple.com/documentation/localauthentication/lacontext
Important
Include the NSFaceIDUsageDescription key in your app’s Info.plist file if your app allows biometric authentication. Otherwise, authorization requests may fail.

UITextField Password Autofill Confirmation

I've been playing around with UITextField's password autofill feature for logging into my backend, and as of yet I've been unable to find a way to actually confirm or validate that the user has authenticated via TouchID to access their passwords.
Am I crazy or because this feature is so baked in to iOS, we can't actually check to see if the user was able to successfully authenticate?
Or am I missing some kind of delegate call in the LocalAuthentication API that gets called?
TIA for your help.
I use a method like this with a callback with the result, sometimes I store off the result to look up later on in the session. Not sure if this is what you're looking for, or if you needed something more advanced. This is part of a class for me where I have made my own delegates that I call on authentication or failure as well.
private func authenticateUser(completion: #escaping (Bool)->()) {
let context = LAContext()
var error:NSError?
let reason = "Authenticate for access"
context.localizedFallbackTitle = ""
if(context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error)){
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply: { (success, error) -> Void in
if(success){
completion(true)
} else {
completion(false)
}
})
}
}

iOS 11 Simulator not allowing LAContext and FaceID

I've running the latest Xcode 9 GM (13 Sep 2017) and have set Hardware > Face ID > Enrolled in simulator as well as Deployment Target 11.0. However I'm getting error code -6 LAErrorTouchIDNotAvailable.
Is there some setting I'm missing?
let myContext = LAContext()
let myLocalizedReasonString = "You are pretty"
var authError: NSError?
if #available(iOS 8.0, macOS 10.12.1, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
if success {
print("// User authenticated successfully, take appropriate action")
} else {
print(" // User did not authenticate successfully, look at error and take appropriate action")
}
}
} else {
print(" // Could not evaluate policy; look at authError and present an appropriate message to user")
}
} else {
print(" // Fallback on earlier versions")
}
Face ID does not work in the Xcode 9 GM due to a framework bug. Xcode 9.1 fixes this issue.
You can test your app in the iPhone 8 simulator and ensure it works correctly with Touch ID or run the Xcode 9.1 beta and test Face ID support there.
I think the iphone X simulator's faceID doesn't work at the moment, hopefully they will fix it soon...
https://forums.developer.apple.com/thread/86779
we could do a bug report to see if it speed things along :P
https://developer.apple.com/bug-reporting
Face ID is working now, with Xcode 9.1. Follow these steps to test it in Simulator.
Add privacy statement in your target's info.plist file.
Import LocalAuthentication framework to your project and add following code to your view controller and try with Face-ID
import LocalAuthentication
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
localAuthentication()
}
func localAuthentication() -> Void {
let laContext = LAContext()
var error: NSError?
let biometricsPolicy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
if (laContext.canEvaluatePolicy(biometricsPolicy, error: &error)) {
if let laError = error {
print("laError - \(laError)")
return
}
var localizedReason = "Unlock device"
if #available(iOS 11.0, *) {
if (laContext.biometryType == LABiometryType.faceID) {
localizedReason = "Unlock using Face ID"
print("FaceId support")
} else if (laContext.biometryType == LABiometryType.touchID) {
localizedReason = "Unlock using Touch ID"
print("TouchId support")
} else {
print("No Biometric support")
}
} else {
// Fallback on earlier versions
}
laContext.evaluatePolicy(biometricsPolicy, localizedReason: localizedReason, reply: { (isSuccess, error) in
DispatchQueue.main.async(execute: {
if let laError = error {
print("laError - \(laError)")
} else {
if isSuccess {
print("sucess")
} else {
print("failure")
}
}
})
})
}
}
}
FaceID authentication will prompt you for first time to allow FaceID detection for your app.
Now enable Face ID enrolment and run your app to test Face ID simulation Testing.
Here is simulation result for matching and non-matching faces.
Result for matching face:
Result for non-matching face:
XCode 9.1 beta came out today in which the original code should work perfectly in the simulator!
According to Apples Documentation for LAContext, we need to add the key NSFaceIDUsageDescription with a reason of use String, as that will display the authorisation request for the use of FaceId on the device.
Example add this to info.plist:
NSFaceIDUsageDescription
set it to type String, and add a text that you want to be shown, in the prompt request for access to the Face ID camera.
"Your app" request your permission to use Face ID, for you to login to your account / unlock your notes / what ever reason in the end.
By adding this, you can go to the simulator for iPhone X, and you will be prompted for the Face ID, press accept, and everything should work perfectly.
Remember to enrol biometry support for the simulator by going into
Simulator -> Hardware -> Face ID / Touch ID -> Enrolled
Then you just need to pressed the Match / Non-Matching Touch / Face ID, to test out your handling
For more details and check out Apple's documentation: https://developer.apple.com/documentation/localauthentication/lacontext
---- Edit ----
This worked for me in both Xcode 9.0 and 9.1

How to use passcode lock scene in my app?

Actually, I building an app which contains local authentication.
My code so far:
func authenticateUser() {
let authenticationContext = LAContext()
var error: NSError?
let reasonString = "Touch the Touch ID sensor to unlock."
// Check if the device can evaluate the policy.
if authenticationContext.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
authenticationContext.evaluatePolicy( .deviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, evalPolicyError) in
if success {
print("success")
} else {
if let evaluateError = error as NSError? {
// enter password using system UI
}
}
})
} else {
print("toch id not available")
// enter password using system UI
}
}
My problem is I want to use the passcode lock scene when the app doesn't has touch ID or invalid finger print.
Like below Image:
How can I do it?
You should use .deviceOwnerAuthentication instead of .deviceOwnerAuthenticationWithBiometrics to evaluate policy. With this parameter the system uses biometric authentication if available else it presents passcode screen. And if the biometric authentication is available but fails, a fallback button redirect to the passcode screen. See documentation :
If Touch ID or Face ID is available, enrolled, and not disabled, the user is asked for that first. Otherwise, they are asked to enter the device passcode.
Tapping the fallback button switches the authentication method to ask the user for the device passcode.
So your code will be:
func authenticateUser() {
let authenticationContext = LAContext()
var error: NSError?
let reasonString = "Touch the Touch ID sensor to unlock."
// Check if the device can evaluate the policy.
if authenticationContext.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: &error) {
authenticationContext.evaluatePolicy( .deviceOwnerAuthentication, localizedReason: reasonString, reply: { (success, evalPolicyError) in
if success {
print("success")
} else {
// Handle evaluation failure or cancel
}
})
} else {
print("passcode not set")
}
}
At this point, I am afraid that you cannot access this passcode lock screen into your app, it is related to the iOS itself. You might need to build your own custom view controller to look/behave as the passcode lock scene (with Touch ID). I would suggest to use a library for achieving this, personally, I've tried PasscodeLock and it works fine for me.

Resources