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
Related
I am using phone authentication using cloud firestore. In firestore db, I am storing user phone number and uid. Here is the code which, I have tried for mobile number login:
#IBAction func signUp(_ sender: Any) {
// dismiss keyboard
view.endEditing(true)
if sendOTP == false {
let mobileNumber = "+91" + phoneNumberTextField.text!
self.Userdefaults.set(mobileNumber, forKey: "mobileNumber")
print("mobileNumber::::\(mobileNumber)")
sendOTPCode()
sendOTP = true
} else {
let codestring = OTPCodeTextField.text
if codestring?.count == 6 {
loginusingOTP(OTPtext: codestring!)
} else {
print("Enter 6 digit code")
}
}
func sendOTPCode() {
let mymobilenumber = Userdefaults.string(forKey: "mobileNumber")
PhoneAuthProvider.provider().verifyPhoneNumber(mymobilenumber!) { (verificationID, error) in
self.Userdefaults.set(verificationID, forKey: "authVerificationID")
if error != nil
{
print ("insde SendCode, there is error")
print("error: \(String(describing: error?.localizedDescription))")
} else {
print ("code sent")
self.phoneNumberTextField.allowsEditingTextAttributes = false
}
}
}
func loginusingOTP(OTPtext: String) {
let db = Firestore.firestore()
let verificationID = self.Userdefaults.string(forKey: "authVerificationID")
let credential: PhoneAuthCredential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID!,
verificationCode: OTPtext)
Auth.auth().signIn(with: credential)
{
(user, error) in
if error != nil
{
print("error: \(String(describing: error?.localizedDescription))")
}
else if user != nil
{
print("Phone number: \(String(describing: user?.phoneNumber))")
let userInfo = user?.providerData[0]
print("Provider ID: \(String(describing: userInfo?.providerID))")
var _: DocumentReference? = nil
print("currentUser:::\(String(describing: currentUser))")
db.collection("users").document(currentUser!).setData([
"User_Phone_number": user?.phoneNumber as Any,
"uid": currentUser as Any
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
if PrefsManager.sharedinstance.isFirstTime == false{
let when = DispatchTime.now() + 0
DispatchQueue.main.asyncAfter(deadline: when) {
self.performSegue(withIdentifier: "signUpToTabBar", sender: nil)
}
}else{
let when = DispatchTime.now() + 0
DispatchQueue.main.asyncAfter(deadline: when) {
let storyboard = UIStoryboard(name: "Start", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "onboardvc")
self.present(initialViewController, animated: true, completion: nil)
}
}
}
}
} else {
print("error::::::")
}
}
}
User login flow - First user enter phone number and then taps on send otp then user enters otp code, logged in successfully. Since user login first time, user needs to fill user detail page and then goes to home page. If its already logged in user, after successful of login user will redirected to home screen not the user detail page.
My question is now user is login each and every time to get inside of the app, I want user to login automatically without login each and every time unless user logout. How to check already logged in user UID or phone in cloud firestore for user exist or new user.
Any help much appreciated pls...
Let me give you brief scenario.
SwiftyUserDefaults is the best library to store UserDefaults throughout the app.
Create one extension like this,
extension DefaultsKeys {
static let username = DefaultsKey<String?>("username")
static let phoneNo = DefaultsKey<String?>("phoneNo")
static let islogin = DefaultsKey<Bool?>("islogin")
}
After successful login, you can set the values of the above DefaultKeys like below, first import SwiftyUserDefaults,
Defaults[.username] = Your_User_Name
Defaults[.phoneno] = Your_Phone_No
Defaults[.islogin] = true
Now on your first LoginViewController, in viewDidLoad() method, Please check following,
if Defaults[.islogin] == true {
//Go to Home with animation false
}
Here you go, let me know in case of any queries.
FYI. This is just the scenario, actual may be different depending on your final requirement. This answer may help you.
For Firebase (and it looks like FireStore). Once the User is Authenticated on the device, they will automatically be "logged in" to Firebase/Firestore next sessions unless there is a specific SignOut (Auth.auth().signOut()) or unless there is a super long delay (not sure how long, maybe a month).
To check to see if the user is already logged in.
On Start Up (didFinishLaunchingWithOptions) set up an Auth Listener and it will fire once and return the current Auth Status.
func addUserListener() {
listenHandler = Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
// We are Logged Out of Firebase.
// Move to Login Screen
} else {
// we are Logged In to Firebase.
// Move to Main Screen
}
}
You really need to use an Auth Listener for Login calls as if someone has two devices and logs out on one device, they will be logged out of firebase and the 2nd device will crash when you try a firebase call, because it still thinks its logged in.
Update your login credentials OR loggedIn flag to user Defaults
UserDefaults.standard.set("value", forKey: "username")
UserDefaults.standard.set("value", forKey: "password")
(NB: i recommended not to store password directly, you could store authentication token in Userdefaults, & if you want password to be stored, use keychain instead)
And redirect to corresponding page from AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var initialViewController: UIViewController?
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let username = UserDefaults.standard.value(forKey: "username"), let password = UserDefaults.standard.value(forKey: "password") {
initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC")
} else {
initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC")
}
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
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).
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.
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)
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.