How do I authenticate users in shoudPerformSegueWithIdentifier and Firebase? - ios

I'm trying to create an authentication page in storyboard using IOS swift and Firebase. Here is my storyboardsegue declaration:
Then I'm using shouldPerformSegueWithIdentifier to authenticate it. However, I also what to log the user in Firebase so my code is like so:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if db.authData != nil {
return true
} else {
let email = emailTextField.text
let password = passwordTextField.text
db.authUser(email, password: password, withCompletionBlock: {
error, authData in
if error != nil {
print(error.description)
} else {
print("logged in")
}
})
return false
}
}
This code somewhat works but I would have to click the log in button twice because the first time logs in the user in Firebase, and the second time the db.authData is no longer nil because the user is already logged in so it returns true. I don't want to have to click twice to log in, I just want to click once. I can't just put return true or return false in the withCompletionBlock either because the block returns void. How do I make this work?

Using current implementation you can't achieve this feature. You have two ways to do this:
Make the db.authUser() synchronous and return it's result
Instead of connecting the login button segue to next screen, add an IBAction method and implement the method like
#IBAction func login(sender : AnyObject?)
{
let email = emailTextField.text
let password = passwordTextField.text
db.authUser(email, password: password, withCompletionBlock: {
error, authData in
if error != nil
{
print(error.description)
}
else
{
// Navigate to next screen
// Start perform segue here
}
})
}

Related

How to solve the problem with Firebase listener, which open everywhere?

I have some simple App, which have auth.
nearest code check if you already entered:
func signingManager(){
Auth.auth().addStateDidChangeListener { [weak self] (auth, user) in
guard let self = self else {return}
if user != nil {
self.showNextVC()
print("You are already entered")
}
}
}
It's works when you first open the app and if you entered func "showNextVC" will open next VC.
In the same time i have login button with code :
#IBAction func logInTapped(_ sender: UIButton) {
guard let email = emailTextField.text, let password = passwordTextField.text, email != "", password != "" else {
displayWarningLabel(withText: "info is incorrect")
return
}
Auth.auth().signIn(withEmail: email, password: password, completion: { [weak self] (user, error) in
if error != nil {
self?.displayWarningLabel(withText: "error occured")
return
}
if user != nil {
self?.showNextVC()
print("Congratulations, you have successfully logged in!")
}
self?.displayWarningLabel(withText: "no such user")
}
)}
Now about the problem: if I click the "login" button, the "signingManager ()" method and it's "showNextVC" are triggered first, and only then the "logInTapped" method itself and again "showNextVC".
As a result, I have 2 VCs and two messages:
"You are already entered" and
"Congratulations, you have successfully logged in!"
What am I doing wrong? Thanks!
Since you're listening for for auth state changes, you don't need to handle the self?.showNextVC() in the completion callback for signIn(withEmail:, password:). That code should only be present in the callback for addStateDidChangeListener.
Alternatively, you can:
Use the addStateDidChangeListener to initially detect whether the user is signed-in already.
Inside the callback for the state change:
Remove the listener by calling removeAuthStateDidChangeListener
Start the explicit sign-in flow, and call signIn(withEmail:, password:) like you're doing now.

IOS app in swift 3 not navigating to further viewcontrollers

I am doing project on secured authentication....when i try to register my email id and password it is not going to next viewcontroller...when i run my code it is working well and not showing any errors but i am not getting output...here is my code
#IBAction func signInButtonTapped(_ sender: UIButton) {
// TODO: Do some form validation on the email and password
if let email = emailTextField.text, let pass = passwordTextField.text {
// Check if it's sign in or register
if isSignIn {
// Sign in the user with Firebase
FIRAuth.auth()?.signIn(withEmail: email, password: pass, completion: { (user, error) in
// Check that user isn't nil
if let u = user {
// User is found, go to home screen
self.performSegue(withIdentifier: "goToHome", sender: self)
}
else {
// Error: check error and show message
self.displayAlertMessage(messageToDisplay: "Password didn't match");
}
})
}
else {
// Register the user with Firebase
FIRAuth.auth()?.createUser(withEmail: email, password: pass, completion: { (user, error) in
// Check that user isn't nil
if let u = user {
// User is found, go to home screen
self.performSegue(withIdentifier: "goToEnroll", sender: self)
}
else {
// Error: check error and show message
}
})
}
}
Please check the connection in Storyboard, make sure that the segue name of link between Current VC -> Home VC is: "goToHome"
Check the same for Enroll VC
Check here if the idenfier is same as you have given in your story board.
Note: Please check first that the identifier you have mentioned here is same as your storyboard one.

Firebase segue authentication

Currently I'm working with a Firebase authentication and handled many errors like wrong username, password, empty fields etc and everything is working fine. However when I try to add a segue to the next page and use random email which is not exist in the database, Firebase auth didn't give me any errors and just pass the user to the next page. When I remove the segue and put random user it gives me an error that could not find such user which is what I'm trying to achieve with the segue. Please advise, what might be the problem?
AuthService file
func login(email: String, password: String, onComplete: Completion?) {
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
self.handleFirebaseError(error: error! as NSError, onComplete: onComplete)
} else {
onComplete?(nil, user)
}
})
}
func handleFirebaseError(error: NSError, onComplete: Completion?) {
print(error.debugDescription)
if let errorCode = FIRAuthErrorCode(rawValue: error.code) {
switch (errorCode) {
case .errorCodeInvalidEmail:
onComplete?("Invalid email address", nil)
break
case .errorCodeWrongPassword:
onComplete?("Invalid password", nil)
break
case .errorCodeUserNotFound:
onComplete?("Can't find user", nil)
break
default:
onComplete?("There was a problem authentication. Try again", nil)
}
}
}
ViewController
#IBAction func loginBtnTapped(_ sender: Any) {
if let email = emailField.text, let pass = passwordField.text , (email.characters.count > 0 && pass.characters.count > 0) {
AuthService.instance.login(email: email, password: pass, onComplete: { (errMsg, data) in
guard errMsg == nil else {
self.alert(title: "Error Authentication", errMsg: errMsg!)
return
}
})
} else {
self.alert(title: "Username and password required", errMsg: "You must enter both a username and a password")
}
}
hybridcattt is correct. I'm going to add a more specific answer. When using segues, make sure it the next screen is not directly connected from the button, otherwise it will fire as soon as you tap it, regardless of the result you're waiting for.
Connect your view controller instead to the next view controller, like so:
And then add an indentifier to that segue.
Finally, when you get what you're waiting for (e.g. successful logging in), then do the performSegue.
If you are creating a segue in a storyboard, it fires immediately and opens the next view controller. Login code you have is async, and storyboard doesn't wait for it.
It is very likely that loginBtnTapped method is not even called if you have a segue configured (try adding a breakpoint there)

Firebase - iOS Swift: FIRAuth.auth().signOut() not signing out current user

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
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.
GIDSignIn.sharedInstance().signOut()
Use exclamation points not question marks.
try! FIRAuth.auth()!.signOut()
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.
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.
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.

get user information using Facebook custom login in Swift

I'm trying to get user information using a custom facebook login with the Facebook SDK for iOS. I successfully log in the user but I don't know how to get access to the user information.
This is my login function:
func facebookLogin() {
if (FBSession.activeSession().state == FBSessionState.Open || FBSession.activeSession().state == FBSessionState.OpenTokenExtended)
{
// Close the session and remove the access token from the cache
// The session state handler (in the app delegate) will be called automatically
FBSession.activeSession().closeAndClearTokenInformation()
}
else
{
// Open a session showing the user the login UI
// You must ALWAYS ask for public_profile permissions when opening a session
FBSession.openActiveSessionWithReadPermissions(["public_profile", "email"], allowLoginUI: true, completionHandler: {
(session:FBSession!, state:FBSessionState, error:NSError!) in
self.sessionStateChanged(session, state: state, error: error)
})
}
}
Then when the session is open the function sessionStateChanged is called:
func sessionStateChanged(session:FBSession, state:FBSessionState, error:NSError?){
if ((error) != nil){
NSLog("Error")
FBSession.activeSession().closeAndClearTokenInformation()
}
else{
if (state == FBSessionState.Open){
//I would like to get the user token or FBGraphUser here but i don't know how
}
}
if (state == FBSessionState.Closed || state == FBSessionState.ClosedLoginFailed){
NSLog("Session Clossed")
}
if (FBErrorUtility.shouldNotifyUserForError(error) == true){
NSLog("Something went wrong")
}
else{
if (FBErrorUtility.errorCategoryForError(error) == FBErrorCategory.UserCancelled){
NSLog("User cancelled login")
}
else if (FBErrorUtility.errorCategoryForError(error) == FBErrorCategory.AuthenticationReopenSession){
NSLog("Current session is no valid")
}
}
}
Ran into the exact same problem today! I'm not sure if this is the ideal way of doing things, but this is the workaround that my colleague showed me:
FBSession.openActiveSessionWithReadPermissions(["public_profile"], allowLoginUI: false) { session, state, error in
let request = FBRequest(session: FBSession.activeSession(), graphPath: "/me")
request.startWithCompletionHandler({ (connection, result, error) -> Void in
println(result)
let birthday = result.valueForKey("birthday") as String
println("\(birthday)")
})
self.fbSessionStateChanged(session, state: state, error: error)
}
Please forgive the ugly code, I'm still very much experimenting with Swift. Hope it helps!
You can use this:
Add FBLoginViewDelegate to the class
Declare this
#IBOutlet var fbLoginView : FBLoginView!
Connect this var (fbLoginView) with a new view in your storyboard. This new view can be hidden so it wont has any effect but helping you to track the information
In your viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
//facebook login
self.fbLoginView.delegate = self
self.fbLoginView.readPermissions = ["public_profile", "email"]
}
And add this function
func loginViewFetchedUserInfo(LoginView : FBLoginView!, user: FBGraphUser){
println("User name: \(user.name)")
//THE USER WAS LOGGED
}
When the user is authenticated by facebook, returns to loginViewFetchedUserInfo method automatically by using the FBLoginViewDelegate and you can get the information with the "user" variable

Resources