`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
Related
In our app, the user has to register to the device biometry in order to use it for authentication.
The registration text and legal notes are according to the relevant biometry (register to touch ID or register to face ID)
As far as I found, the biometry type is obtainable via the LAContext, but if the user denies usage of biometry, then the context returns biometryType=.none
Any ideas other that asking for the screen size and comparing to iphone X (bad bad code)?
static fileprivate var biometryType: DSLocalAuthenticationBiometryType {
let context = LAContext()
var error: NSError?
let _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
if #available(iOS 11.0, *) {
return context.biometryType == .typeFaceID ? .typeFaceID : .none
}
else {
return .none
}
}
Thanks
I've got the same identical issue, and I've just found out that if you evaluate against the key LAPolicyDeviceOwnerAuthentication instead of LAPolicyDeviceOwnerAuthenticationWithBiometrics, even after the user declined the permission, the evaluation succeeds and you get the correct biometryType. Your code would be like
static fileprivate var biometryType: DSLocalAuthenticationBiometryType {
let context = LAContext()
var error: NSError?
let _ = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error)
if #available(iOS 11.0, *) {
return context.biometryType == .typeFaceID ? .typeFaceID : .none
}
else {
return .none
}
}
NOTE: on devices without touch id and face id, it still returning YES, so you would not know whether the device really has a biometric hw or not with iOS lower than 11 (which do not expose the property biometriyType)
Update
For devices with iOS version 10 or lower, you can use the
LAPolicyDeviceOwnerAuthenticationWithBiometrics as usual, it will behave correctly (returning true whether the device supports the touch Id), so it's just a matter of differentiating the running OS version :)
Let me know if it works :)
Best
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
The following tests works fine on iOS 11. It dismisses the alert asking permissions to use the locations services and then zooms in in the map. On iOS 10 or 9, it does none of this and the test still succeeds
func testExample() {
let app = XCUIApplication()
var handled = false
var appeared = false
let token = addUIInterruptionMonitor(withDescription: "Location") { (alert) -> Bool in
appeared = true
let allow = alert.buttons["Allow"]
if allow.exists {
allow.tap()
handled = true
return true
}
return false
}
// Interruption won't happen without some kind of action.
app.tap()
removeUIInterruptionMonitor(token)
XCTAssertTrue(appeared && handled)
}
Does anyone have an idea why and/or a workaround?
Here's a project where you can reproduce the issue: https://github.com/TitouanVanBelle/Map
Update
Xcode 9.3 Beta's Changelogs show the following
XCTest UI interruption monitors now work correctly on devices and simulators running iOS 10. (33278282)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.waitForExistence(timeout: 10) {
allowBtn.tap()
}
Update .exists to .waitForExistence(timeout: 10), detail please check comments.
I had this problem and River2202's solution worked for me.
Note that this is not a fix to get the UIInterruptionMonitor to work, but a different way of dismissing the alert. You may as well remove the addUIInterruptionMonitor setup. You'll need to have the springboard.buttons["Allow"].exists test anywhere the permission alert could appear. If possible, force it to appear at an early stage of the testing so you don't need to worry about it again later.
Happily the springboard.buttons["Allow"].exists code still works in iOS 11, so you can have a single code path and not have to do one thing for iOS 10 and another for iOS 11.
Incidentally, I logged the base issue (that addUIInterruptionMonitor is not working pre-iOS 11) as a bug with Apple. It has been closed as a duplicate now, so I guess they acknowledge that it is a bug.
I used the #River2202 solution and it works better than the interruption one.
If you decide to use that, I strongly suggest that you use a waiter function. I created this one in order to wait on any kind of XCUIElement to appear:
Try it!
// function to wait for an ui element to appear on screen, with a default wait time of 20 seconds
// XCTWaiter was introduced after Xcode 8.3, which is handling better the timewait, it's not failing the test. It uses an enum which returns: 'Waiters can be used with or without a delegate to respond to events such as completion, timeout, or invalid expectation fulfilment.'
#discardableResult
func uiElementExists(for element: XCUIElement, timeout: TimeInterval = 20) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == true"), object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
guard result == .completed else {
return false
}
return true
}
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.
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