Firebase segue authentication - ios

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)

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.

Firebase Email already in use error

I am trying to login to my user after updating firebase, and after tracing the error, I get the following error:
Error Domain=FIRAuthErrorDomain Code=17007 "The email address is
already in use by another account."
UserInfo={NSLocalizedDescription=The email address is already in use
by another account., error_name=ERROR_EMAIL_ALREADY_IN_USE}
After looking it seems to be a because that firebase user is already in use, I am not sure how to fix this. I believe it is because I never signed out the user before closing app, but not am unable to login as any of my users.
Below is my code:
#IBAction func Login(sender: AnyObject) {
let email = self._Email.text!
let password = self._Password.text!
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if error == nil {
//successfull login
print("Successful login****************************************")
//performs a segue to the next view controller
if user!.isEmailVerified{
//if the email is verified
let vc = self.storyboard!.instantiateViewController(withIdentifier: "ProfileView") as! ProfileView
self.present(vc, animated: true, completion: nil)
}
else {
print("email is not verified")
}
} else {
print("Some login error")
}
}
}
As ZassX pointed out, you're indeed using the signUp method of the Firebase iOS SDK, which is the createUserWithEmail. Use this method instead to signIn using email and password:
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
// ...
}
More info: https://firebase.google.com/docs/auth/ios/password-auth
You can check the list of your registered users in your Firebase Authentication Dashboard (https://console.firebase.google.com)
Also, it is good to print out the error description if you're having an error object. Like so:
print(error.localizedDescription).

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.

How to persist Firebase authdata after Force Quit (iOS)

I am using email + password authentication with Firebase for my app. Login works, and I use observeAuthEventWithBlock to check if a user is logged in - in order not to bring up the Login page. If I press the home button and open the app again, there is no problem. The problem I am having is if I force-quit the app. When I re-open, I have to log-in again.
Some notes about the setup before I show my login code.
There is a LoginViewController - not embedded - built w/ Storyboard
This is connected to a Navigation Controller
Which is what the first screen is embedded in, and the rest of the app uses this Nav Controller.
Login code:
#IBAction func loginButtonPressed() {
let userEmail = emailTextField.text
self.ref.authUser(self.emailTextField.text, password: self.passwordTextField.text, withCompletionBlock: { (error, auth) -> Void in
guard error == nil else {
if let errorCode = FAuthenticationError(rawValue: error.code) {
switch (errorCode) {
case .EmailTaken:
self.displayMessage("Email Error", theMessage: "This email is taken")
case .InvalidEmail:
self.displayMessage("Email Error", theMessage: "This email is invalid")
case .UserDoesNotExist:
self.displayMessage("User Error", theMessage: "A user account for email: \(userEmail!) does not exist")
case .InvalidPassword:
self.displayMessage("Password Error", theMessage: "The password is incorrect")
case .NetworkError:
self.displayMessage("Network Error", theMessage: "Seems like there's a problem with your internet connection")
default:
return
}
}
return //set Unknown Error Alert here
}
print("LOGGED IN: segue from loginButtonPressed")
self.userLoggedIn = true
print("user is logged in? \(self.userLoggedIn)")
self.performSegueWithIdentifier("loginLocaleSegue", sender: self)
})
}
Check if user is logged in - if so segue to navcon, pop it and display embedded View Controller:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if self.userLoggedIn.boolValue == true {
ref.observeAuthEventWithBlock { (authData) -> Void in
if authData != nil {
let navCon: UINavigationController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("MainNavigationController") as! UINavigationController
self.presentViewController(navCon, animated: false, completion: nil)
navCon.popViewControllerAnimated(false)
print("user is authenticated: \(authData.providerData["email"] as! String)")
print("segues from viewDidAppear")
} else {
return
}
}
}
}
I've seen questions related to Firebase auth which state that Authdata is stored in Keychain by default, which causes problems with Authdata persisting even after deletion of app, but I'm experiencing the total opposite issue. Any ideas?
From what I can tell, you're not writing "self.userLoggedIn = true" to any database, so it makes sense that it continues being "True" while you have the app on idle, but once you close the application, it then becomes nil (no value), this is because it's just chilling in the background while the app is open, but not completely closed. Try writing this to your Firebase database, as outlined in this tutorial, and see if that helps.
https://www.raywenderlich.com/109706/firebase-tutorial-getting-started

How do I authenticate users in shoudPerformSegueWithIdentifier and Firebase?

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

Resources