Authentication failed and Try Face Id Again does nothing - ios

I'm just follow this tutorial to authenticate user by Face ID but actually it's not work with "Try Face Id Again" button after authentication failed, the callback was not called and I don't know why.
Here is the code:
#IBAction func touchIdAction(_ sender: UIButton) {
print("hello there!.. You have clicked the touch ID")
let myContext = LAContext()
let myLocalizedReasonString = "Biometric Authntication testing !! "
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
DispatchQueue.main.async {
if success {
// User authenticated successfully, take appropriate action
self.successLabel.text = "Awesome!!... User authenticated successfully"
} else {
// User did not authenticate successfully, look at error and take appropriate action
self.successLabel.text = "Sorry!!... User did not authenticate successfully"
}
}
}
} else {
// Could not evaluate policy; look at authError and present an appropriate message to user
successLabel.text = "Sorry!!.. Could not evaluate policy."
}
} else {
// Fallback on earlier versions
successLabel.text = "Ooops!!.. This feature is not supported."
}
}
Running with Xcode 9.4.1 on iPhone X Simulator.
Thanks

Before tapping Try Face Id Again button you have to inform the simulator if it should simulate matching or non-matching face.
You can do it by selecting Hardware->Face ID->Matching Face/Non-matching Face

ANSWER:
I had the same problem. Then I checked in device and it works. So this doesn't work only in simulator

Related

How to run a function after setupIntentConfirmation succeeds?

I can successfully create and confirm a setupIntent. After the user enters his credit card details and presses the "confirm" button, his payment method is successfully attached to their stripe customer account. However, when the setupIntent is confirmed, I'd like to run a function on my client to send the user to a different screen.
This is where I check the completion of the setupIntentConfirmation. The print statement shows in the console but for some reason, my function is not running.
func onCompletion(status: STPPaymentHandlerActionStatus, si: STPSetupIntent?, error: NSError?) {
self.setupIntentStatus = status
self.error = error
if status == .succeeded {
print("succeeded")
setIsVerifiedToTrue()
}
}
Here is the function I am trying to run:
func setIsVerifiedToTrue() {
let ref = FirebaseReferenceManager.root.collection(FirebaseKeys.CollectionPath.requirements).document(Auth.auth().currentUser!.uid)
ref.updateData([FirebaseKeys.RequirementsFieldPath.verified : true]) { error in
if let error = error {
print("Error updating document: \(error)")
} else {
print("isVerified set to true")
}
}
}
any help would be very much appreciated! :)

iOS Biometric authentication: Try Password work only after the second biometric fail attempt

I'm trying out the biometric authentication on iOS for the first time.
My touch id authentication code is working perfectly. But if touch id fails, I want to authenticate using Device PIN. But this is working only after the second touch id attempt fail. First time, when it fails, an alert shows with 'Try Password' button. But when touching it, instead of going to the screen to enter device pin, it shows the enter touch id alert again.
Now if the touch id fails again and if I touch the Enter password button. It's going to the screen to enter Device PIN.
But why is it not working the first time? From Apple docs:
The fallback button is initially hidden. For Face ID, after the first
unsuccessful authentication attempt, the user will be prompted to try
Face ID again or cancel. The fallback button is displayed after the
second unsuccessful Face ID attempt. For Touch ID, the fallback button
is displayed after the first unsuccessful Touch ID attempt.
I see it working with apps like google pay. What am I doing wrong here.
Here's my code.
public partial class AuthenticationViewController : UIViewController
{
private LAContext context;
public AuthenticationViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (UserDefaultsManager.RememberMe)
TryAuthenticate();
else
AppDelegate.Instance.GotoLoginController();
}
private void TryAuthenticate()
{
context = new LAContext();
NSError error = null;
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0) &&
context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out error)) {
// Biometry is available on the device
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics,
"Unlock SmartFHR", HandleLAContextReplyHandler);
} else {
// Biometry is not available on the device
if (error != null) {
HandleLAContextReplyHandler(false, error);
} else {
TryDevicePinAuthentication(error);
}
}
}
private void TryDevicePinAuthentication(NSError error)
{
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthentication, out error)) {
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthentication,
"Unlock SmartFHR", HandleLAContextReplyHandler);
}
}
private void HandleLAContextReplyHandler(bool success, NSError error)
{
DispatchQueue.MainQueue.DispatchAsync(() => {
if (success) {
ContinueAfterAuthSuccess();
return;
}
switch (error.Code) {
case (long)LAStatus.UserCancel:
AppDelegate.Instance.GotoLoginController(true);
break;
case (long)LAStatus.UserFallback:
case (long)LAStatus.BiometryNotEnrolled:
case (long)LAStatus.BiometryNotAvailable:
TryDevicePinAuthentication(error);
break;
}
});
}
private void ContinueAfterAuthSuccess()
{
if (Storyboard.InstantiateViewController("SplashController") is SplashController vc)
AppDelegate.Instance.Window.RootViewController = vc;
}
}
When first touch id attempt fails and I touch the Try Password button, I see that it calls the HandleLAContextReplyHandler with error code LAStatus.UserFallback.
The documentation for LAPolicyDeviceOwnerAuthentication says:
If Touch ID or Face ID is available, enrolled, and not disabled, the
user is asked for that first.
So when you used TryDevicePinAuthentication to display an authentication window, it will still show a biometric window first.
If you want the user to enter the passcode to go through the authentication, I think DeviceOwnerAuthentication is enough.
private void TryAuthenticate()
{
context = new LAContext();
// if Biometry is available on the device, it will show it first
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthentication, "Unlock SmartFHR", HandleLAContextReplyHandler);
}
In this way, you only need to handle the situation that the user cancels this authentication. Because it will automatically popup a passcode entering window when the user clicks the fallback button:
private void HandleLAContextReplyHandler(bool success, NSError error)
{
DispatchQueue.MainQueue.DispatchAsync(() => {
if (success)
{
ContinueAfterAuthSuccess();
return;
}
switch (error.Code)
{
case (long)LAStatus.UserCancel:
AppDelegate.Instance.GotoLoginController(true);
break;
default:
break;
}
});
}

iOS, using biometric authentication only for a subset of an apps functionality

I'm working on an app where the user needs to be authenticated for only some of the features, but they are supposed to remain 'anonymous' for the rest of the features. Is there a way to implement biometric authentication within the app when the user tries to access specific features?
For instance, let us say that to participate in an open discussion forum within the app they do not need to authenticate, but once they try to send a direct message they will need to authenticate using Touch ID or Face ID. Can this be done, or can biometric authentication only be used when the user opens the app?
Yes, you can use it anywhere you want to. Just ask for authentication and then, after authentication is successfully completed, show another ViewController.
you can do biometric auth anywhere you want, Here is a sample that you can try:
func authenticationWithTouchID(completion: (Bool) -> ()) {
let localAuthenticationContext = LAContext()
localAuthenticationContext.localizedFallbackTitle = "Use Passcode"
var authError: NSError?
let reasonString = "To access the secure data"
if localAuthenticationContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
localAuthenticationContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString) { success, evaluateError in
if success {
completion(true)
} else {
//TODO: User did not authenticate successfully, look at the error and take appropriate action
guard let error = evaluateError else { return }
print(self.evaluateAuthenticationPolicyMessageForLA(errorCode: error._code))
//TODO: If you have choosen the 'Fallback authentication mechanism selected' (LAError.userFallback). Handle gracefully
completion(false)
}
}
} else {
guard let error = authError else { return }
//TODO: Show appropriate alert if biometry/TouchID/FaceID is lockout or not enrolled
print(self.evaluateAuthenticationPolicyMessageForLA(errorCode: error.code))
}
}
func evaluatePolicyFailErrorMessageForLA(errorCode: Int) -> String {
var message = ""
if #available(iOS 11.0, macOS 10.13, *) {
switch errorCode {
case LAError.biometryNotAvailable.rawValue:
message = "Authentication could not start because the device does not support biometric authentication."
case LAError.biometryLockout.rawValue:
message = "Authentication could not continue because the user has been locked out of biometric authentication, due to failing authentication too many times."
case LAError.biometryNotEnrolled.rawValue:
message = "Authentication could not start because the user has not enrolled in biometric authentication."
default:
message = "Did not find error code on LAError object"
}
} else {
switch errorCode {
case LAError.touchIDLockout.rawValue:
message = "Too many failed attempts."
case LAError.touchIDNotAvailable.rawValue:
message = "TouchID is not available on the device"
case LAError.touchIDNotEnrolled.rawValue:
message = "TouchID is not enrolled on the device"
default:
message = "Did not find error code on LAError object"
}
}
return message;
}
func evaluateAuthenticationPolicyMessageForLA(errorCode: Int) -> String {
var message = ""
switch errorCode {
case LAError.authenticationFailed.rawValue:
message = "The user failed to provide valid credentials"
case LAError.appCancel.rawValue:
message = "Authentication was cancelled by application"
case LAError.invalidContext.rawValue:
message = "The context is invalid"
case LAError.notInteractive.rawValue:
message = "Not interactive"
case LAError.passcodeNotSet.rawValue:
message = "Passcode is not set on the device"
case LAError.systemCancel.rawValue:
message = "Authentication was cancelled by the system"
case LAError.userCancel.rawValue:
message = "The user did cancel"
case LAError.userFallback.rawValue:
message = "The user chose to use the fallback"
default:
message = evaluatePolicyFailErrorMessageForLA(errorCode: errorCode)
}
return message
}
Now You can use this function anywhere you want
authenticationWithTouchID(completion: {(success) in
if success {
//TODO: User authenticated successfully, take appropriate action
} else {
//TODO: User authenticateion failed, take appropriate action
}
})

When presenting Touch ID prompt, is it possible to immediately offer the user the chance to enter their passcode?

When presenting Touch ID / Local Authentication to unlock something, and both Touch ID and Passcode are possible, it will only show the "Enter Password" option after the first attempt at Touch ID fails.
Is it possible to show it right away? I'm envisioning a user who isn't authenticated to use Touch ID on the device tries to use the app, and if they know the passcode it's totally okay to be using it, and they may not try their finger print as they know it's not added.
Yes it's possible Touch ID with Passcode
Works with Apple Face ID (iPhone X, Xs, XR, XsMax) and other Touch ID having devices.
Predefined error handling when recognition fails.
Automatic authentication with device passcode on multiple failed attempts.
Use Apple FaceID or TouchID authentication in your app using BiometricAuthentication. It's very simple and easy to use that handles Touch ID and Face ID authentication based on the device.
Please check this example :-
Apple Touch ID
Two option's available
.deviceOwnerAuthenticationWithBiometrics
deviceOwnerAuthentication
extension ViewController {
func authenticationWithTouchID() {
let localAuthenticationContext = LAContext()
localAuthenticationContext.localizedFallbackTitle = "Use Passcode"
var authError: NSError?
let reasonString = "To access the secure data"
if localAuthenticationContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authError) {
localAuthenticationContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reasonString) { success, evaluateError in
if success {
print("User authenticated successfully")
//TODO: User authenticated successfully, take appropriate action
} else {
//TODO: User did not authenticate successfully, look at error and take appropriate action
guard let error = evaluateError else {
return
}
print(self.evaluateAuthenticationPolicyMessageForLA(errorCode: error._code))
//TODO: If you have choosen the 'Fallback authentication mechanism selected' (LAError.userFallback). Handle gracefully
}
}
} else {
guard let error = authError else {
return
}
//TODO: Show appropriate alert if biometry/TouchID/FaceID is lockout or not enrolled
print(self.evaluateAuthenticationPolicyMessageForLA(errorCode: error.code))
}
}
func evaluatePolicyFailErrorMessageForLA(errorCode: Int) -> String {
var message = ""
if #available(iOS 11.0, macOS 10.13, *) {
switch errorCode {
case LAError.biometryNotAvailable.rawValue:
message = "Authentication could not start because the device does not support biometric authentication."
case LAError.biometryLockout.rawValue:
message = "Authentication could not continue because the user has been locked out of biometric authentication, due to failing authentication too many times."
case LAError.biometryNotEnrolled.rawValue:
message = "Authentication could not start because the user has not enrolled in biometric authentication."
default:
message = "Did not find error code on LAError object"
}
} else {
switch errorCode {
case LAError.touchIDLockout.rawValue:
message = "Too many failed attempts."
case LAError.touchIDNotAvailable.rawValue:
message = "TouchID is not available on the device"
case LAError.touchIDNotEnrolled.rawValue:
message = "TouchID is not enrolled on the device"
default:
message = "Did not find error code on LAError object"
}
}
return message;
}
func evaluateAuthenticationPolicyMessageForLA(errorCode: Int) -> String {
var message = ""
switch errorCode {
case LAError.authenticationFailed.rawValue:
message = "The user failed to provide valid credentials"
case LAError.appCancel.rawValue:
message = "Authentication was cancelled by application"
case LAError.invalidContext.rawValue:
message = "The context is invalid"
case LAError.notInteractive.rawValue:
message = "Not interactive"
case LAError.passcodeNotSet.rawValue:
message = "Passcode is not set on the device"
case LAError.systemCancel.rawValue:
message = "Authentication was cancelled by the system"
case LAError.userCancel.rawValue:
message = "The user did cancel"
case LAError.userFallback.rawValue:
message = "The user chose to use the fallback"
default:
message = evaluatePolicyFailErrorMessageForLA(errorCode: errorCode)
}
return message
}
}

How to identify iCloud logged in user in airplane mode?

I try to get userRecordID in airplane mode, but I get an error, any other way?
class func asdf() {
var defaultContainer = CKContainer.defaultContainer()
var publicDatabase = defaultContainer.publicCloudDatabase
defaultContainer.fetchUserRecordIDWithCompletionHandler({ userRecordID, error in
if error == nil {
println("userRecordID.recordName : \(userRecordID.recordName)")
} else {
println("\(error.localizedDescription)")
}
})
}
Terminal: Couldn't renew our secure session
I put an accountStatusWithCompletionHandler call outside of fetchUserRecordIDWithCompletionHandler, that returned CKAccountStatus.Available.
You cannot detect internet connectivity with CloudKit. It will only give you an error when there is no connectivity. If you do want to test for internet connectivity, then you could use the famous Reachability class like this: How to check for an active Internet connection on iOS or OSX?
If you want to detect changes to the iCloud account, then you can add the following code to your AppDelegate application didFinishLaunchingWithOptions:
var localeChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(NSUbiquityIdentityDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()) { _ in
println("The user’s iCloud login changed: should refresh all user data.")
}
If you then want to fetch the user id, you have to do a container.requestApplicationPermission to see if you are allowed to query an then a container.fetchUserRecordIDWithCompletionHandler. Bit this requires internet connection. You could cache it on the device together with the detection code above to get the correct status.
I came across to this code, comparing recently and previous logged in user's token, and if the same, use the previously downloaded userRecordID. The only problem that in some cases on my iPad ubiquityIdentityToken method returns nil even dow I am logged in, strange.
class func checkUser() {
let ubiquityIdentityToken = NSFileManager.defaultManager().ubiquityIdentityToken
let status = Utility.status()
let prevUbiquityIdentityToken = status.objectForKey("ubiquityIdentityToken")
if ubiquityIdentityToken != nil && ubiquityIdentityToken!.isEqual(prevUbiquityIdentityToken) {
} else if ubiquityIdentityToken != nil && !ubiquityIdentityToken!.isEqual(prevUbiquityIdentityToken) {
status.setObject(ubiquityIdentityToken!, forKey: "ubiquityIdentityToken")
Utility.saveStatus(status)
let defaultContainer = CKContainer.defaultContainer()
let publicDatabase = defaultContainer.publicCloudDatabase
defaultContainer.fetchUserRecordIDWithCompletionHandler({ userRecordID, error in
if error == nil {
//do some stuff
})
} else {
println("\(error.localizedDescription)")
}
})
} else {
//do some stuff
status.removeObjectForKey("ubiquityIdentityToken")
Utility.saveStatus(status)
}
}

Resources