Problem Description: I have a main App which is widely used and handles touch id based login with a registration flow.
At the time of touch id registration within my app, we store the evaluatedDomainState in the app user defaults.
When the customer uses Touch ID/ face ID to login, we check 2 things on the device before invoking server
1. Touch id verification
2. Comparing evaluatedDomainState for identifying fingerprint changes on device. If evaluatedDomainState doesn't match, we ask the customer to re-register touch id.
We are enhancing this app by adding an keyboard extension. The keyboard will use the same login flow and user defaults.
In the login process via keyboard,
point 1 is successful. Point 2 fails from keyboard extension although successful from main app.
Here is the code.
self.touchIDHash = self.context.evaluatedPolicyDomainState;
NSData *savedHash = [[NSUserDefaults standardUserDefaults] objectForKey:#"TouchIdHash"];
BOOL fingerPrintChanged = savedHash && ![savedHash isEqualToData:self.touchIDHash];
fingerPrintChanged is always true from keyboard extension login and false from main app login.
Any inputs to make this work will be much helpful.
Due to fingerPrint is setup in your container APP,
verify it in container app then navigate back to extension, maybe it's workaround for you.
Related
I am implementing biometric login into my iOS app in Swift, and I have use this API :
var permissions = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,error: &failureReason)
to check whether biometric is supported. It is working normal if Face ID permission was allowed. However, after uninstall and rerun from XCode, with calling canEvaluatePolicy without any permission prompt, it still return the supported biometric in response closure. I was wondering what is the different between before allowed permission and after allowed permission.
May i know any solution/ way to know the permission state for this, thank you.
When you make a call such as:
var permissions = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &failureReason)
then iOS will run through the following checks:
Does the device have Face ID capability?
Yes: Is Face ID setup on the device?
Yes: Is the app's Face ID permission enabled?
Yes: return true
No: Fallback to device passcode
No: Fallback to device passcode
No: Fallback to Touch ID
Does the device have Touch ID capability?
Yes: Is Touch ID setup on the device?
Yes: Is the app's Touch ID permission enabled?
Yes: return true
No: Fallback to device passcode
No: Fallback to device passcode
No: Fallback to device passcode
Does the device have a passcode setup?
Yes: return true
No: return false (no Face ID, no Touch ID, no passcode)
When you call evaluatePolicy the first time after a fresh install of the app, iOS will prompt the user whether it should allow Face ID (if the device supports Face ID and Face ID has been setup on the device) and it will show the app-provided reason string entered into Info.plist for the "Privacy - Face ID Usage Description".
The user selection affects the app's Face ID permission. Once the user makes their choice, the evaluation goes through the same logic as shown above. Depending on the result, iOS will either authenticate via Face ID, Touch ID, or passcode.
If you delete an app and then do a fresh install via Xcode then the whole process starts over and the user will be prompted again on first use.
The biometryType value of LAContext is independent of the app's permission. It's what the device supports which is either Face ID, Touch ID, or neither. The value allows your app to use appropriate messaging in your user interface.
For example, you can use the value to label a switch as either "Enable Face ID Login" or "Enable Touch ID Login".
The app's permission ultimately will determine if either Face ID or Touch ID (based on the capabilities of the device) will end up falling back to just asking for the passcode.
When logging a user in or signing the user up for the first time and using phone auth it says:
Save the verification ID and restore it when your app loads. By doing
so, you can ensure that you still have a valid verification ID if your
app is terminated before the user completes the sign-in flow (for
example, while switching to the SMS app).
I'm not exactly sure what is the purpose of saving it.
0- the user opens the app and they are on the Login screen
1- the user adds their phone number (below)
2- the callback receives the authVerificationID
3- before the user is taken to the SMS screen, the app is somehow terminated
4- when the user opens the app again, because they haven't logged in yet, they are right back on the Login screen. When they enter their phone number again, they receive either a brand new authVerificationID or the same one (I'm not sure), and they are taken to the SMS screen. Either way both are valid and will get them to the SMS screen.
What does saving the authVerificationID do when no matter what, if the app is terminated, they have to add their phone number again because they will be back on the Login screen?
If the answer is "check if the authVerificationID is saved, and if it is then bring them to the SMS screen instead of the Login screen" then that is bad ux. The user might come back an hour later, when they first open the app they will see the SMS screen and be confused.
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
guard let verificationID = verificationID else { return }
UserDefaults.standard.set(verificationID, forKey: "authVerificationID") // how does this help me?
// A. take user to sms view controller
// B. use authVerificationID && verificationCode for sms sign-in
}
The answer is pretty much what you said - if the user leaves your app in the middle of the auth flow, you can you can resume the sign-in flow from where they left off - i.e. the SMS verification step.
Once the user leaves your app, you have no guarantees that your app will not be terminated. It depends entirely on the OS. So your user might end up in a cycle where they just can't complete the sign-up (however unlikely this case is).
As for bad UX comment - it's very subjective (and depends on your design), and also time-dependent.
Im trying to find a way to de-authorise users for push notifications when they log out from an app Ive found this function UIApplication.shared.unregisterForRemoteNotifications() which aparently works however I never see notifications being disabled, I also read in the documentation that it should not be used often or something to that effect, I basically want to have a toggle button in my app where the user clicks it one way and gets the standard enable notifications popup and another way to disable notifications on the fly, Im not a native swift developer so any pointers welcome
Also is it possible to attach a callback to this to know if it executes successfully, Im trying the following but get the error Argument passed to call that takes no arguments
UIApplication.shared.unregisterForRemoteNotifications() { (result, error) in
if let error = error {
call.error("Error", error)
} else if let result = result {
call.success([
"deregister": true
])
}
}
Edit: I found this which says its not possible to toggle on and off Change push notifications programmatically in Swift
With that in mind does this mean that the standard for devices is that:
1) when a user log out of their account they can still receive notifications.
2) When a user creates a new account on the same app it uses the same token and so receives notifications from the old account ?
3) when a user sells their phone and another guy/gal downloads the same app that they will receive notifications from that other users account (in terms of 3rd party push service one signal, aparently you dont need to refresh the player id)
XCode 9 (currently in beta) has a method called activate that can be used to bring an application to the foreground:
https://developer.apple.com/documentation/xctest/xcuiapplication/2873317-activate
Using the following code, I put the application into the background using the home button and then bring the application back to the foreground:
XCUIDevice.shared().press(XCUIDeviceButton.home)
let app = XCUIApplication(bundleIdentifier:"com.aaa.abc.xyz")
app.activate()
When the application is brought to the foreground, the user is taken back to the login screen. It behaves as if it does not have access to the token anymore which the tokens are currently stored in NSUserdefaults. Does activate not check here for tokens or clear it?
Using app.launch() again does work but curious why activate() does not?
Scenario:
I want to call the logout function if the app is terminated. I'm able to do it using native code:
- (void)applicationWillTerminate:(UIApplication *)app
{
// Run Logout function
}
Problem:
How to do it in IBM mobilefirst hybrid app?
// ************************************************
Edited
First of all, user login in to the app, if the user key in the correct user id and password, it will add the userIdentity into "loginRealm".
WL.Server.setActiveUser("loginRealm", userIdentity);
Next, user closes the apps without logout. So, when the user login for the another time, MFP server will not return any feedback since it will hit this exception:
Cannot change identity of an already logged in user in realm
'loginRealm'. The application must logout first.
Hence, I have to logout the user from MFP server by setting the "loginRealm" to null in adapter;
WL.Server.setActiveUser("loginRealm", null);
The above line of code is in the logout function defined in authentication-config.xml.
The client side device runs this line of code and it will trigger the logout function. Besides, it will reload the App upon success:
WL.Client.logout('loginRealm', {
onSuccess: WL.Client.reloadApp
});
Steps that I've tried:
1) At WlcommonInit() I added WL.Client.updateUserInfo(); and if WL.Client.isUserAuthenticated("loginRealm") return true I will logout the user from server. However, WL.Client.isUserAuthenticated("loginRealm") will always return false. This is because, it needs to take sometime around (30seconds to 2 minutes) for the flag to turn true after WL.Client.updateUserInfo();. So my login still fail and hit the same error.
2) I tried to logout the users during the user click login button. But the app will refresh and return to login page again due to reloadApp. The logout code I get from IBM mobilefirst website. So user need to click and type 2 times in order to login into the main menu.
WL.Client.logout('loginRealm', {
onSuccess: WL.Client.reloadApp
});
Am I doing it wrongly? Or are there any other methods to get WL.Client.isUserAuthenticated("loginRealm") return true instantly after WL.Client.updateUserInfo(); ? Can we remove the reload app line of code in logout function?
I don't think this is doable, because that logout function (in MFP) will require server connectivity (request and response) and if the app is by then killed, I think it's going to cause unpredictable results.
Note though that it seems to be not recommended to use that function anyway? applicationWillTerminate when is it called and when not
What you should do perhaps in order to simulate it, is to logout-on-login, so that it would appear that the app is logged out when opening it. You can extend the duration of the splash screen so that the end-user will not see that s/he is logged in (in case the session was still alive between the closing and re-opening of the app), until really logged out and then you can display the login screen again or any other required screen.