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.
Related
I'm struggling to debug a hard-to-reproduce-locally crash in my app. The crash reports show the exception type is EXC_BAD_ACCESS (SIGSEGV) with objc_msgSend at the top of the crashed thread, perhaps suggesting "The process may have attempted to message a deallocated object" (as per Apple's documents).
I have not been able to recreate the crash or find any zombies using the Zombies instrument, despite using an older device and the same simulated device causing the most crashes.
From the trace I'm pretty sure I've been able to work out where the crash is occurring, but I'm not sure what I'm doing wrong.
My app is basically a group database. The app takes a username and password, sending them to a server to check if the person can be logged in. If valid, the app downloads the data on all the people in the group, then finds the logged in person from the downloaded data based on their username.
Because of the way the server code is set up, the username must be present in the downloaded data (if it all downloads correctly), yet my code occasionally fails to find them, and (from the crash logs) also occasionally this attempted finding of the person causes a crash.
coreDataStack.storeContainer.performBackgroundTask() { context in
OverallNetworkCalls.deleteAllLoadPeople(moContext: context) { result in
self.coreDataStack.saveNewContext(context: context)
if result == .success {
let person = AdministrativeHelperFunctions.returnCurrentUserFromUserName(moContext: context)
if let person = person {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
}
UserDefaults.standard.set(person.objectID.uriRepresentation(), forKey: udl.CurrentUserMOID.rawValue)
} else {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.loadingLabel.text = "Data was downloaded but your username isn't there. Try deleting and reloading the app, or contact admin."
}
}
}
func saveNewContext(context: NSManagedObjectContext) {
context.perform {
guard context.hasChanges else { return }
do {
try context.save()
} catch {
//logs the issue locally
}
}
}
func returnCurrentUserFromUserName(moContext: NSManagedObjectContext) -> Person? {
let userName = UserDefaults.standard.string(forKey: "username")
if let userName = userName {
//Create predicate, arrayForResults etc
do {
userNameMatchesArray = try moContext.fetch(fetchRequest)
} catch {
//logs the issue locally
}
if userNameMatchesArray.count == 1 {
return userNameMatchesArray[0]
} else {
return nil
}
} else {
return nil
}
}
My suspicion is that the returnCurrentUserFromUserName function is returning the user before the saveNewContext function has completed its task and that this is contributing to the issue, though given the same managedObjectContext is saving then retrieving the person I had originally assumed this wasn't a problem. I'm also not sure that using "context.perform" in the saveNewContext function is necessary/wise/useful; I'd value feedback on this too if anyone knows best practice inside a performBackgroundTask block.
A non-reproduceable bug is always the most frustrating to fix, and because of this anything I try to fix it won't be shown until I send it to my beta testers and get crashlogs back (or not!).
Thanks in advance for reviewing this problem.
I've implemented password/TouchID/FaceID on a view controller and when I hit the success case, I'd expect the prompt to stop firing but it just fires over and over again.
In my VC:
var context: LAContext!
func authenticateReturningUser() {
context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Verify that this is your device to continue."
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
DispatchQueue.main.sync {
guard success else {
guard let error = error else {
// show error
return
}
switch error {
case LAError.userCancel:
// do stuff
return
default: return
}
}
print("success")
}
}
}
}
The prompt should fire once and not again if the user successfully authorizes
Edit:
authenticateReturningUser is called from the AppDelegate's applicationDidBecomeActive function:
self.coverVC?.completionHandler = { self.removeBackgroundVC() }
self.coverVC?.authenticateReturningUser()
As far as I remember, when showing the Touch ID prompt, your app becomes inactive. So when the prompt is dismissed, your app becomes active again, triggering the App Delegate's applicationDidBecomeActive again.
You might consider introducing a flag that stores whether the app became inactive because of Touch ID / Face ID etc. or because of another reason and use it in applicationDidBecomeActive to decide if authentication should be triggered or not.
Where are you calling authenticateReturningUser()? You may want to create a static boolean authenticated that if false, allows the call to authenticateReturningUser(), and if true, skips the call, and set authenticated = true after calling the function once.
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)
}
})
}
}
I want to use TouchID in my app and I've found some weird behaviour. So in general when TouchID alert is shown it and user try to authenticate with fingerprint canEvaluatePolicy will fail only after 3 attempts but if user go to Settings>Touch ID & Passcode and turn off "use touch id for: iPhone Unlock" canEvaluatePolicy will fail after first attempt with error message "Biometry is disabled for unlock.". Does anyone know is it a bug or it is by design.
Also looks like it happens only on iOS 11.
Here is code that i use for TouchID configuration
var error: NSError?
context.localizedFallbackTitle = "fallback title"
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
DispatchQueue.main.async {
if success {
// success case
}
else {
// error handling
}
}
}
}
else {
// error handling
}
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.