I am currently using MSAL signout function in my iOS app and have no way of knowing if the user selects 'Continue' or 'Cancel'. Currently, once log out is completed the user is taken back to the root screen of the app. However, I need to be able to stop the flow so that if the user chooses to cancel, they are still on the same screen before choosing the log out option.
private func signout(with account: MSALAccount, signoutParameters: MSALSignoutParameters, completion: #escaping () -> Void) {
self.application.signout(with: account, signoutParameters: signoutParameters, completionBlock: { (result, error) in
if error != nil {
return
}
SharedStorage.authData = nil
completion()
})
}
The issue I have is that result returns true whether the user chooses continue or cancel and error returns nil. Curious if anyone has run into this issue before?
Related
I have a ViewController with a Sign in button used to sign in into Firebase with a Google Account:
GIDSignIn.sharedInstance().signIn()
When I click the button, this appears:
Google account choosing
After selecting an account and if the authentication is successful, I want to load a second ViewController. For this, I have a listener in the first ViewController that will sign in again when the authentication state changes, this time successfully, without asking the account again and sending me directly to the second ViewController:
Auth.auth().addStateDidChangeListener({ auth, user in
if let _ = user {
GIDSignIn.sharedInstance().signIn()
}
})
The problem is that I want an activity indicator to be shown when I go back to the first ViewController from the account chooser. Because the app may be there for a few seconds during the authentication process and I don't want the user to tap again the Sign In button, while the first signing in hasn't already finished.
I need a way to recognise that a signing in process is taking place, to show an activity indicator that locks the screen to prevent the user from tapping again Sign in.
WORKAROUND 1
When I click the Sign in with Google button, I set an UserDefaults integer as 1. Then, when the ViewController is reloaded after the Google account chooser, I look for this integer and if it's 1, I don't stop the activity Indicator.
Because I want the activity indicator shown since the user clicks the button until the authentication is completed.
When button is clicked I do:
GIDSignIn.sharedInstance().signIn()
UserDefaults.standard.set(1, forKey: "signingIn")
UserDefaults.standard.synchronize()
In viewWillAppear I do:
if let _ = user {
GIDSignIn.sharedInstance().signIn()
} else {
if UserDefaults.standard.integer(forKey: "signingIn") != 1 {
self.stopActivityIndicator()
} else {
UserDefaults.standard.set(0, forKey: "signingIn")
UserDefaults.standard.synchronize()
}
}
When the authentication is completed, in GIDSignInDelegate there is the function that will be called. In this function, the activity indicator must be stopped:
// The sign-in flow has finished and was successful if |error| is |nil|.
- (void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error;
WORKAROUND 2
I do a put the signIn Google function into a completion handler but it doesn't work:
self.completionHandlerSigIn {
self.stopActivityIndicator()
}
And the function is this:
func completionHandlerSigIn(completion: () -> Void) {
GIDSignIn.sharedInstance().signIn()
}
The problem is that the view is reloaded during the sign in process, after the account choosing. I need a way to recognize that I come from the Google Account choosing screen.
Just show the loading indicator right when the user clicks sign in, then hide it either when the authentication process returns with error or after processing the result. I don't use google sign in, but I can give you my example with Twitter.
#IBAction func onTwitterClicked(_ sender: UIButton) {
AuthManager.shared.loginWithTwitter(self)
}
Here is the loginWithTwitter method in AuthManager:
func loginWithTwitter(_ viewController:BaseController) {
self.provider = .twitter
viewController.showLoadingPanel()
TWTRTwitter.sharedInstance().logIn(completion: {session, error in
guard (error == nil) else {
viewController.hideLoadingPanel()
viewController.showInfoAlert("Oops", error!.localizedDescription, nil)
return
}
let credential = TwitterAuthProvider.credential(withToken: session!.authToken, secret: session!.authTokenSecret)
self.auth.signIn(with: credential, completion: {user, error in
viewController.hideLoadingPanel()
guard error == nil else {
viewController.showInfoAlert("Oops", error!.localizedDescription, nil)
return
}
self.tryConfirmUserInFirestore(user, viewController)
})
})
}
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)
}
})
}
}
There is a bug that's happening in my app. I suspect it has something to do with slow internet connection, and since the user isn't getting a response, they keep hitting the sign up button. I'll go look in the DB, and see like 12 accounts for the same email all signed up the same time. How do you guys handle this? Is there a way I can do a spinner until the Firebase login/createAccount method is finished?
Firstly change your one account per email address to yes.
When your user presses the button to create an account , you need to trigger the createUser but before that you need to disable the button itself to avoid the further calls from the same user with same details
func createMyUser(sender:UIButton!){
sender.isEnabled = false
FIRAuth.auth()?.createUser(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, err) in
....
}
Now if you encounter any error even the network error handle that error with errorCodeNetworkError. And show the user an alert stating a network error has occurred.
if let firebaseError = FIRAuthErrorCode(rawValue: err!._code) {
switch firebaseError{
case .errorCodeEmailAlreadyInUse : sender.isEnabled = true
break
case .errorCodeNetworkError : print("Network Error occured!")
sender.isEnabled = true
break
default : .....
}}
I downloaded some sample code about twitter login code and run it in Xcode, when I run the App, a button appeared in the screen, and when I click it, I cannot login twitter successfully, the code goes to the else path, the code associated with the button is like below:
#IBAction func signInWithTwitter(sender: UIButton) {
Twitter.sharedInstance().logInWithCompletion { session, error in
if session != nil {
// Navigate to the main app screen to select a theme.
self.navigateToMainAppScreen()
// Tie crashes to a Twitter user ID and username in Crashlytics.
Crashlytics.sharedInstance().setUserIdentifier(session!.userID)
Crashlytics.sharedInstance().setUserName(session!.userName)
// Log Answers Custom Event.
Answers.logLoginWithMethod("Twitter", success: true, customAttributes: ["User ID": session!.userID])
} else {
// Log Answers Custom Event.
Answers.logLoginWithMethod("Twitter", success: false, customAttributes: ["Error": error!.localizedDescription])
}
}
}
Has anyone faced this problem? Who can help me out, thanks in advance.
For my app I need to save one page of it with TouchID. So the user is kinda forced to use TouchID or if the device does not support it, a password. If the user cancels the TouchID authentication, I want to View to disappear and get back to the root view. I already had that working, but somehow it does not work anymore and I really don't know why?! I just copied until the canceled option, the rest is does not matter I guess.
func authenticateUser() {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your App"
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error){
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("authentification successful")
}
}else{
switch policyError!.code{
case LAError.SystemCancel.rawValue:
print("Authentification was canceled by the system")
case LAError.UserCancel.rawValue:
print("Authentication was canceled by user")
self.navigationController?.dismissViewControllerAnimated(true, completion: nil)
//Yes I also tried popToRootViewController, still doesn't work
}
The documentation for the evaluatePolicy call says:
"Reply block that is executed when policy evaluation finishes. This block is evaluated on a private queue internal to the framework in an unspecified threading context."
So the problem is that you are trying to call navigation from the wrong thread. You need to make that call on the UI thread. For example:
dispatch_async(dispatch_get_main_queue()) {
// Navigation goes here
}