Removing Firebase authState upon viewDidDisappear - ios

I'm using Firebase's new framework and I'm attempting to monitor the login state of the user on both the Login and Signup VC's separately. The problem is if the login state changes on the SignUp view the Auth State on the Login view gets called as well. My question is, how do I remove the auth state? I found the syntax on the Firebase website but am a little confused on what to pass in considering my code for the auth state:
FIRAuth.auth()?.addAuthStateDidChangeListener { auth, user in
if let theUser = user {
// User is signed in.
print("LOGGED IN!!!! :::: \(theUser)")
self.dismissViewControllerAnimated(true, completion: nil)
} else {
// No user is signed in.
print("Need to login first.")
}
}
Code to use to remove the auth, but unsure what to pass in.
FIRAuth.auth()?.removeAuthStateDidChangeListener(FIRAuthStateDidChangeListenerHandle)
Says I pass in a FIRAuthStateDidChangeListenerHandle, but how do I obtain this, or do I rewrite my authState code differently?

Just store the auth in a variable
self.authListener = FIRAuth.auth()?.addAuthStateDidChangeListener { auth, user in
if let theUser = user {
// User is signed in.
print("LOGGED IN!!!! :::: \(theUser)")
self.dismissViewControllerAnimated(true, completion: nil)
} else {
// No user is signed in.
print("Need to login first.")
}
}
and remove it later
FIRAuth.auth()?.removeAuthStateDidChangeListener(self.authListener)

Related

Firebase Auth Login must allow single device login

I am developing app with the help of Firebase backend and I am using Firebase Auth for login into my application. I have done all integration and every thing and my app is working fine.
But I want only single session with single user as right now with single userId I am able to login through multiple devices.
So I want to restrict user that at a time user can login in in single device.
I am using Custom auth with username password login :
Auth.auth().signIn(withCustomToken: customToken ?? "") { (user, error) in
// ...
}
If user login with same id in another device I want to show alert that "You are already logged in another device".
Is there any possibility in Firebase Auth lib for single user single session?
Edit : Suggested duplicate question will not solve my query fully though it help me to understand scenireo and help to solve my problem.
Thanks #Frenk for pointing this out.
I search a lot on above issue which I was facing through firebase authentication and after lots of research I ended up with below solution which was working as per my requirements.
First of all firebase not providing this in their library so we need to apply our custom logic here to achieve this 1 session user login in our app.
Step 1: You need to add new child "SignIn" at your root of Database.
Step 2: While Auth.auth().signIn() return success in that block we need to check below Flag that is User already signIn in any other device ? for that I have create one method as mention below.
func alreadySignedIn() {
// [START single_value_read]
let userID = Auth.auth().currentUser?.uid
ref.child("SignIn").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let dict = snapshot.value as? [String: Any] {
if let signedIn = dict["signIn"] as? Bool {
if signedIn {
self.signOut()
}
else {
// change the screen like normal
print("First Session of user")
self.writeNewUserSigin(withUserID: userID!)
}
}else{
self.writeNewUserSigin(withUserID: userID!)
}
}else{
print(snapshot)
self.writeNewUserSigin(withUserID: userID!)
}
}) { (error) in
print(error.localizedDescription)
}
// [END single_value_read]
}
By this method we are checking that current user uId have in our SignIn Child with True value if data is there in our database with Boll value True we need to handle that and show some alert and signOut from firebase.
Note : As we allowed user to sign-in and than we are checking that
user already signin in any other device so if its returning True we
need to SignOut() from firebase.
Now last step while user manually signOut from the app
Step 3: While user click on SignOut button in app we need to update our Child with False value in it so after onwards user can able to SignIn in any other device. For that we can use below method.
func updateUserSigIn(withUserID userID: String) {
//Update SignIn Child with flase value on current UID
// [START write_fan_out]
let post = ["signIn": false]
let childUpdates = ["/SignIn/\(userID)": post]
let ref = Database.database().reference()
ref.updateChildValues(childUpdates) { (error, refDatabase) in
if (error != nil) {
print("error \(String(describing: error))")
}else {
print("New user Saved successfully")
self.signOut()
}
}
// [END write_fan_out]
}
Thats it now only one app user session will allow.
Hope this will helps others.
Thanks for this thread as I got some hints from this answer.

iOS Firebase sign in. Show activity indicator after Google account choosing

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)
})
})
}

Twitter / Fabric login button not behaving as expected

I have a very simple iOS project where I'm using Twitter/Fabric login button for user login to my app.
I've managed to get the Fabric login button working. When the user clicks on the Twitter login button they are automatically authenticated (that's if they are logged into the Twitter app) otherwise the user is presented with a Twitter login screen.
I'm not sure why the user is automatically authenticated when they are logged into the Twitter app on their phone.
Is there a way to use the Twitter/Fabic API to open the Twitter app and ask for permission to grant access to my app similar to Facebook login even if the user is logged into the Twitter App.
This is what my AppDelegate looks like:
Twitter.sharedInstance().start(withConsumerKey: "someKey", consumerSecret: "someSecret")
Fabric.with([Twitter.self])
This is what my ViewController looks like:
#IBOutlet private weak var twitterLoginButton: TWTRLogInButton!
// and
twitterLoginButton.logInCompletion = {(session, error) in
if error != nil {
print("ERROR: \(error)")
} else {
if let unwrappedSession = session {
print(unwrappedSession.userName)
}
}
}
Twitter.sharedInstance().logIn { (session, error) in
if let unwrappedSession = session {
print("Signed in as: \(unwrappedSession.userName)")
} else {
print("ERROR: \(error)")
}
}
Fabric documentation says that the first default for login is to go through the Twitter app (that may be why your user is automatically authenticated if they're already logged in the app), otherwise it will go through the webAuth login flow.
"To force the log in flow to use the web OAuth flow pass the TWTRLoginMethodWebBased method to the relevant log in methods."
// If using the TWTRLoginButton
let logInButton = TWTRLogInButton() { session, error in
}
logInButton.loginMethods = [.webBased]
So if you want to force the user to go through the web flow, try adding to your code: twitterLoginButton.loginMethods = [.webBased]

Sign in with different google account Firebase iOS

I am new to iOS and Firebase in general and I'm struggling to find out how I can get the app to show the google oAuth modal when I click the signin button. At the moment, it comes up on the first signup instance, but I have to delete the app to get it working again. This can be a bit cumbersome if someone wants to change google accounts.
// Google Sign In
#IBAction func gooSignInBtn(sender: AnyObject) {
GIDSignIn.sharedInstance().signIn()
}
When I call sign out, it signs out, but the below modal doesn't show up again. It just automatically signs into the last signed in google account.
Does the try! FIRAuth.auth()!.signOut() function only sign out the member temporarily?
#IBAction func signOut(sender: AnyObject) {
try! FIRAuth.auth()!.signOut()
FIRAuth.auth()?.addAuthStateDidChangeListener({ (auth: FIRAuth, user: FIRUser?) in
if let user = user {
// User is signed in.
print(user)
} else {
// No user is signed in.
print("user signed out")
}
})
}
Try adding GIDSignIn.sharedInstance().signOut() for signout

How to change a parse's Facebook account custom fields

I am trying to enable login with a Facebook account for my new social app but I need to know how to change custom Facebook accounts fields in parse so I can know if a Facebook user already set up his account or not. I managed to do it with regular accounts but now with Facebook accounts
Here is my method:
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) {
(user: PFUser?, error: NSError?) -> Void in
if let user = user {
if user.isNew {
println("User signed up and logged in through Facebook!")
self.performSegueWithIdentifier("procceedToSetup", sender: self)
// I need to change here a parse field named "doneSetUp" with a type of a bool to false.
} else {
self.performSegueWithIdentifier("procceedToApp", sender: self)
}
} else {
println("Uh oh. The user cancelled the Facebook login.")
}
}
When a Facebook user completes the setup he/she will press the complete button and it will change the variable "doneSetUp" to true, I do it so Facebook accounts won't exit the app in the middle of the setup and when they log in back they will access the app without setup.
By the way, I would like to know how can I access Facebook account fields I chose with the permissions array, such as country, profile picture, name, etc...
Thanks for all the helpers!
Jeez, I found the reason.
forgot to put
PFUser.currentUser()?.save()
at the end.

Resources