6

I'm building an app using Firebase with an initial SignInViewController that loads a sign in page for users to authenticate with email which triggers the following methods:

@IBAction func didTapSignIn(sender: AnyObject) {
    let email = emailField.text
    let password = passwordField.text
    FIRAuth.auth()?.signInWithEmail(email!, password: password!) { (user, error) in
        if let error = error {
            print(error.localizedDescription)
            return
        }
        self.signedIn(user!)
    }
}

func signedIn(user: FIRUser?) {
    AppState.sharedInstance.displayName = user?.displayName ?? user?.email
    AppState.sharedInstance.signedIn = true
    NSNotificationCenter.defaultCenter().postNotificationName(Constants.NotificationKeys.SignedIn, object: nil, userInfo: nil)
    performSegueWithIdentifier(Constants.Segues.SignInToHome, sender: nil)
}

The SignInViewController also checks if there is a cached current user when the app launches and, if so, signs that user in:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(true)
    //Synchronously gets the cached current user, or null if there is none.
    if let user = FirebaseConfigManager.sharedInstance.currentUser {
        self.signedIn(user)
    }
}

Once the user is signed in, the app segues to a HomeScreenViewController which displays a "Sign Out" button at the top left of the navigation bar. When a user taps the "Sign Out" button, that user is supposed to get signed out and the app should segue back to the SignInViewController with the following method:

@IBAction func didTapSignOut(sender: UIBarButtonItem) {
    print("sign out button tapped")
    let firebaseAuth = FIRAuth.auth()
    do {
        try firebaseAuth?.signOut()
        AppState.sharedInstance.signedIn = false
        dismissViewControllerAnimated(true, completion: nil)
    } catch let signOutError as NSError {
        print ("Error signing out: \(signOutError)")
    } catch {
        print("Unknown error.")
    }
}

When I tap the "Sign out" button, the didTapSignOut method gets called and gets executed. However, after the try firebaseAuth?.signOut() line of code gets executed, the current user should be nil. But when I print out the current user in the Xcode console, the current user is still logged in:

po FIRAuth.auth()?.currentUser
▿ Optional<FIRUser>
  - Some : <FIRUser: 0x7fde43540f50>

Since the current user doesn't get signed out after firebaseAuth?.signOut() gets called, once the app segues back to the SignInViewController the app still thinks there is a cached current user so that user gets signed in again.

Could this be a Keychain issue?

Does it have to do with NSNotificationCenter.defaultCenter().postNotificationName being called?

My code comes directly from the Google Firebase Swift Codelab so I'm not sure why it's not working: https://codelabs.developers.google.com/codelabs/firebase-ios-swift/#4

alexisSchreier
  • 677
  • 1
  • 5
  • 21
  • http://stackoverflow.com/a/39170080/6297658 – Dravidian Sep 02 '16 at 23:51
  • Thanks @Dravidian, I'll take a look. – alexisSchreier Sep 03 '16 at 00:07
  • @Dravidian, thanks for the reference. However, even when I try printing the currentUser in the viewWillAppear(animated:Bool) method after the app segues back to the sign in page, the current user is not nil. Are you saying that because of the asynchronous completion of the .signOut() method, the user isn't getting signed out in time before the viewWillAppear(animated:Bool) method checks if that user is still signed in? If so how would I make sure to execute the check for the current user **only if** the signOut() method was successful? – alexisSchreier Sep 03 '16 at 00:17
  • 1
    It wont be , because your segueing back and forth are carried out by synchronous calls. Try commenting the `self.signedIn(user)` in your viewDidAppear and then try printing the current user , Just as a proof of concept , let m knw....Do upvote the answers that help... – Dravidian Sep 03 '16 at 08:06
  • So any idea on how to make the signout works? – Rudy Feb 06 '17 at 18:02

6 Answers6

6

You can add a listener in your viewDidAppear method of your view controller like so:

FIRAuth.auth()?.addStateDidChangeListener { auth, user in
    if let user = user {
        print("User is signed in.")
    } else {
        print("User is signed out.")
    }
}

This allows you to execute code when the user's authentication state has changed. It allows you to listen for the event since the signOut method from Firebase does not have a completion handler.

Nick Kohrn
  • 5,779
  • 3
  • 29
  • 49
4
GIDSignIn.sharedInstance().signOut()
ChavirA
  • 707
  • 8
  • 18
2

Use exclamation points not question marks.

  try! FIRAuth.auth()!.signOut()
rmickeyd
  • 1,551
  • 11
  • 13
  • Thanks for your answer @rMickeyD. I'm still getting the same result though. I edited my code to `try! FIRAuth.auth()!.signOut()` like you suggested but the current user still exists in cache when viewWillAppear(animated:Bool) gets called and checks for the user in the `SignInViewController`. – alexisSchreier Sep 03 '16 at 02:28
  • never Force unwrap (`!`) a `try`, your app will crash if you force unwrap and the `signOut` does encounter an error. For example, `signOut` will most definitely throw an error if the user has no internet connectivity (or in Airplane mode), in this case your app will most definitely crash. – Anjan Biswas Nov 27 '17 at 07:50
2

I actually had this issue as well. I was also logging out the user (as you are) with the method's provided by Firebase but when I printed to the console it said that I still had a optional user.

I had to change the logic of setting the current user so that it is always configured by the authentication handler provided by Firebase:

var currentUser: User? = Auth.auth().currentUser
var handle: AuthStateDidChangeListenerHandle!

init() {

    handle = Auth.auth().addStateDidChangeListener { (auth, user) in
        self.currentUser = user

        if user == nil {
             UserDefaults.standard.setValue(false, forKey: UserDefaults.loggedIn)
        } else {  
             UserDefaults.standard.setValue(true, forKey: UserDefaults.loggedIn)
        }

    }

}

As long as you are referencing the current user from this handle, it will update the current user no matter the authentication state.

Montchat
  • 21
  • 1
  • 3
2

Some answers are using a force unwrap when the firebase signing out method can throw an error. DO NOT DO THIS!

Instead the call should be done in a do - catch - block as shown below

   do {
        try Auth.auth().signOut()
    } catch let error {
        // handle error here
        print("Error trying to sign out of Firebase: \(error.localizedDescription)")
    }

You can then listen to the state change using Auth.auth().addStateDidChangeListener and handle accordingly.

Edward
  • 2,864
  • 2
  • 29
  • 39
0

I just had what I think is the same problem - Firebase + Swift 3 wouldn't trigger stateDidChangeListeners on logout, which left my app thinking the user was still logged in.

What ended up working for me was to save and reuse a single reference to the FIRAuth.auth() instance rather than calling FIRAuth.auth() each time.

Calling FIRAuth.auth()?.signOut() would not trigger stateDidChangeListeners that I had previously attached. But when I saved the variable and reused it in both methods, it worked as expected.

Andy Miller
  • 849
  • 1
  • 9
  • 21