How to access Firebase Firestore data when login in using Firebase Auth? - swift

On an application I'm building, I need to access the stored documents on Firestore after someone logs in using Auth. I have stored data on Firestore when someone creates an account: name, phone, email. I want to access this information when they log back in somewhere else. Is there a way to link the two on account creation or is there some other way to access the documents?
After looking around on the internet for a while, I haven't found any questions related to this, other than trying to login using Firestore, but it had no answers.
Account creation code:
Auth.auth().createUser(withEmail: email.text!, password: pass.text!) { (user, error) in
if user != nil {
self.performSegue(withIdentifier: "toFinishCreation", sender: self)
} else {
}
}
Storing data on Firestore:
FirebaseFirestore.root.collection("users").document(username).setData([
"name": username,
"phone": userphone,
"email": useremail], completion: { (err) in
if let err = err {
print(err.localizedDescription)
} else {
print("Document added!")
}
}
)
(Btw I used a struct on another swift file using vars called FirebaseFirestore and root)
Login code:
Auth.auth().signIn(withEmail: loginEmail.text!, password: loginPassword.text!) { (user, error) in
if user != nil {
self.performSegue(withIdentifier: "toHome", sender: self)
} else {
}
}
To summarize, how do I access the data that I stored on account login?
Can anyone help me with this? Anything is appreciated!

To fetch a document of the logged-in user you required document id which is unique. So, add user id as a document id. You will get user id in response while logged in and sign up and then, you can use that id to fetch logged in the user's document.

When creating a user, create a new Auth in FirebaseAuth as well as the Firestore. Once the user is logged in with the email address, find the user with that email. Although I have not used firestore before, note that this code is done with firebase database instead but the same should apply:
Auth.auth().createUser(withEmail: email, password: password, completion: { (user: User?, error) in
if error == nil {
print("You have successfully signed up")
guard let uid = user?.uid else {
return
}
//successfully authenticated user
let values = ["name": name, "email": email, "isShop" : shopAccount]
let ref = Database.database().reference()
let usersReference = ref.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
ref.child("Orders").child("Total Orders").setValue(0)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "MainPageViewController")
self.present(vc!, animated: true, completion: nil)
})
This will then create a user in the FirebaseAuth, and add a new user to the Users section of the database under the uid that is assigned when the account is created. Then you can access this later when the user logs back in as you can retrieve the uid, and find the part of the database containing the information using this code:
dataRef.child("users").child(userID!).observeSingleEvent(of: .value, with: {(snapshot) in
As a result, this can be applied to firestore by adding a new document and title to each user added to your FirebaseAuth in order to retrieve future information

Related

iOS Google Auth: "The password is invalid or the user does not have a password"

My app has three login options: email, google, and facebook, all via Firebase. Everything works perfectly except for logging in eith email.
Recreate the problem
Create a user with email (first name, last name, email, password), (lets say with the email, test1#gmail.com)
Sign out and return back to login page
Sign in with google using the same email (test1#gmail.com)... this will give an alert that says that a user already uses that email. It does not redirect user to the home screen.
Sign in with email again (same email and password)
User cannot sign in now. User is shown the warning that "The password is invalid or the user does not have a password"
In Firebase Authentication, the user who used to have an email sign next to it now has a Google sign next to it. However, in Firestore, the firstName, lastName, email, password, and uid is still stored.
My code
Sign in with Google
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let err = error {
print("Failed to log into Google: ", err)
return
}
print("Successfully logged into Google")
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
// sign user in with Firebase
Auth.auth().signIn(with: credential, completion: { (user, error) in
let firstName = user?.user.displayName
let email = user?.user.email
let lastName = ""
let uid = user?.user.uid
if let err = error {
print("Failed to create a Firebase User with Google account: ", err)
return
} else {
// Successfully logged in
print("Successfully logged into Firebase with Google email: ", email ?? "", "Now add user to Firestore if user is new.")
// check if user already exists
self.addUserToFirestore(firstName ?? "", lastName, email ?? "", uid ?? "", "Google")
}
})
}
func addUserToFirestore(_ firstName:String, _ lastName:String, _ email:String, _ uid:String, _ signInMethod:String) {
let db = Firestore.firestore()
let docRef = db.collection("users").document(uid)
// check if user exists in firestore
docRef.getDocument { (document, error) in
if let document = document {
if document.exists {
let message = "Good news! You already have a Coal account that uses " + email + ".\nPlease sign in to your existing account. Then you will be able to link your " + signInMethod + " profile from your Account Settings page."
// user exists. send to chats screen.
print("User already exists. Document data: \(String(describing: document.data()))")
self.showError("You're already a member!", message)
} else {
// user does not exist. create a new user
print("Document does not exist. Create new user.")
docRef.setData(["firstname":firstName, "lastname":lastName, "email":email]) { err in
if err != nil {
// Show error message
print("Error saving user data to Firestore")
} else {
print("New user created in Firestore")
self.transitionToConvo()
}
}
}
}
}
}
Sign in with email function
// Signing in the user
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
// Couldn't sign in
self.loginErrorLabel.text = error!.localizedDescription
self.loginErrorLabel.alpha = 1
}
else {
// user signed in successfully, go to TabBarController
print("User signed in")
let tabBarC = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") as! TabBarController
tabBarC.modalPresentationStyle = .fullScreen
self.present(tabBarC, animated: true, completion: nil)
print("Switched to TabBarController")
}
}
Sign up with email
// Create the user
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in
// there is an error creating user
if err != nil {
self.showErrorLeft("There was an issue creating your account.\n\nPossible reasons for this isssue:\n1) Your email is not formatted correctly.\n2) You already have a Coal account that uses " + email + ". Please sign in to your existing account.\n\nIf you think this is a mistake, please contact as at coal.britto#gmail.com.")
}
else {
let uid = result!.user.uid
// User was created successfully, now store the first name, last name, email
print("new email created in firestore", uid)
db.collection("users").document(uid).setData(["firstname":firstName, "lastname":lastName, "email":email, "password": password, "uid":uid]) { err in
if err != nil {
// Show error message
print("Error saving user data to Firestore")
} else {
print("New user created in Firestore")
self.transitionToConvo()
}
}
// Transition to the chats screen
self.transitionToConvo()
}
}
A User signing in with Google on an E-Mail address that already exists as an E-Mail/PW User will not throw an error. Instead the user is transitioned into a Google Signin User.
So the behaviour you experience is intended by Firebase.
The reason why the password etc. is still stored in your firestore database is because you dont overwrite it with the new data in your code.
Your code after a Google signin sees that there is a document for that user already (the one you created in your createUser function) so nothing happens in your db.
NOTE: Please do not store clear text passwords in your database.

How do I grab credential values for re authentication? (Question updated)

I am trying to allow users to change their email or password. I did some research on how re authentication work, most of the questions I had were answered however when creating the credential how do I grab the users email / password in the .credentialWithEmail(email, password: password) section? I'm not sure what to enter in those fields.
When I take a look at the quick help tab, it explains the Parameters:
email
The user's email address.
password
The user's password.
Here is the code
let credential = FIREmailPasswordAuthProvider.credentialWithEmail(email, password: password)
func updateEmail() {
guard let updatedEmail = self.updatedEmail else { return }
let user = Auth.auth().currentUser
guard let currentUid = Auth.auth().currentUser?.uid else { return }
// re authenticate the user
user?.reauthenticate(with: credential, completion: { (result, error) in
if let error = error {
// An error happened.
print(error._code)
self.handleError(error)
} else {
guard self.emailChanged == true else { return }
user?.updateEmail(to: self.emailTextField.text!, completion: { (error) in
if let error = error {
self.handleError(error)
} else {
print("Email Change Success")
USER_REF.child(currentUid).child("email").setValue(updatedEmail) { (err, ref) in
self.dismiss(animated: true, completion: nil)
}
}
}
)}
}
)}
If you are asking why you can't get the email or password after you set FIREmailPasswordAuthProvider, it is because it is set only. There is no function to retrieve email/password after you set FIREmailPasswordAuthProvider. Firebase does not have a way to retrieve set passwords from their Auth class. The user email can be retrieved with let userEmail = Auth.auth().currentUser?.email
You may need to redesign you code to allow the app to save passwords locally using NSUserDefaults or simply make the user reenter them.

Firebase sendEmailVerification prior to createUser

Is there a way in Firebase to verify a user's email prior to adding the user to the database?
My ultimate goal is to create a SignUp form where the user first enters the email address, then presses a "Verify Email address" button. At this point the verification email will be sent and the user will follow the link from within to confirm the email address. Now, when going back to the app, the user will have a continue button, and if the the email is not verified, the user will not be able to register, if contrary, the user will be able to register.
How do I go about this?
So far all the documentation says that I must createUser so then I can use currentUser.sendEmailVerification, but obviously, I do not want create a user, before verification.
I also thought about using a completion block, but I am not sure how to work that out, because the registration would have to be postponed until the user presses the continue button.
Thanks
Auth.auth().createUser(withEmail: email, password: password, completion: {(user: User?, error) in
if error != nil {
print(error!)
return
}
guard let uid = user?.uid else {
return
}
let ref = Database.database().reference(fromURL: "https://project/")
let values = [
"email": email,
"userName": userName,
"name": name,
"birthDate": birthDate,
"phoneNumber": phoneNumber]
let userReference = ref.child("users").child(uid)
userReference.updateChildValues(values, withCompletionBlock: {(err, ref) in
if err != nil{
print(err!)
return
}
print("Successfully added user to database")
})
let usedUserReference = ref.child("users-Used").child(userName)
usedUserReference.setValue(uid, withCompletionBlock: {(err, ref) in
if err != nil{
print(err!)
return
}
print("Successfully added user to cannot be used-again list")
})
})
You can't do this with Firebase Auth. The user needs to be created first, because that record is the "container" that gives the email verification the appropriate context about which user has actually been verified when they respond to the email.
In your app, you can check to see if a particular user account has been verified by using the emailVerified property. This allows you to give the end user different experience with respect to their verification.

How to check if user needs to re-authenticate using Firebase Authentication

I am using Firebase to log in users into my app, but when I am adding the capability to manage their account like changing their email, password and so on. The documentation says that if the user have not recently signed in they need to re-authenticate, but my question is: How can I check if the user have signed in recently or not? According to the docs the error will return FIRAuthErrorCodeCredentialTooOld, but how can I check this?
Swift 3
I had to do this yesterday when trying to delete a user. One thing to note is FIRAuthErrorCodeCredentialTooOld is now FIRAuthErrorCode.errorCodeRequiresRecentLogin
What I did was trigger a UIView to ask for log in details if that error is thrown. Since I was using email and password, that's what I collected from the user in my example.
private func deleteUser() {
//get the current user
guard let currentUser = FIRAuth.auth()?.currentUser else { return }
currentUser.delete { (error) in
if error == nil {
//currentUser is deleted
} else {
//this gets the error code
guard let errorCode = FIRAuthErrorCode(rawValue: error!._code) else { return }
if errorCode == FIRAuthErrorCode.errorCodeRequiresRecentLogin {
//create UIView to get user login information
let loginView = [yourLoginUIViewController]
self.present(loginView, animated: true, completion: nil)
}
}
}
Once I had the login information I ran this function to reauthenticate the user. In my case I ran it the loginView in the above code if the login in was successful:
func reauthenticateUserWith(email: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if error == nil {
//display UIView to delete user again
let deleteUserView = deleteUserView()
present(deleteUserView, animated: true, completion: nil)
} else {
//handle error
print(error!.localizedDescription)
}
}
}
The deleteUserView in my case calls deleteUser() on a button tap from the user. You can also use UIAlertController in place of the custom UIViews, but that's up to you.
Hope this helps.
Update for current Swift 5
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
let authErr = AuthErrorCode(rawValue: error.code)
if authErr == .requiresRecentLogin {
// reauthenticate
}
// other error
} else {
// delete success
}
}
According to the documents, there is currently no way to check FIRAuthErrorCodeCredentialTooOld other than going through the deleting of the account or the other sensitive cases mentioned.
If you are like me and ended up here because you are trying to figure out how to handle removing someone from Auth and removing other user data in Cloud Firestore, Realtime Database, and/or Cloud Storage, then there is a better solution.
Check out the Delete User Data Extension from Firebase to handle this. In short, when a user profile is deleted from Auth, you can use this also to delete data associated with the uid from those other Firebase data storage tools.

Firebase authentication: linking multiple accounts in Swift

I've set up Firebase authentication for my iOS app using Facebook, Google & email/password sign in and it's all working fine. This authentication only happens when the user wants to access high-priority parts of my app (i.e. I don't require users to sign in to start using the app).
On app start up, I sign users in anonymously in the background and that's working fine too.
I've read the documentation but I'm struggling to understand the code required to enable me to link an anonymous account to a Facebook/email signed in account in the following flow:
new user opens app
user signed in anonymously in the background (new user.uid "A" created)
low priority data stored against anonymous user in Firebase realtime DB
user hits a high-priority area so needs to authenticate
user signs in using Facebook (new user.uid "B" created)
previous user.uid "A" needs to be linked to user.uid "B"
My method currently looks like this:
func signupWithFacebook(){
// track the anonymous user to link later
let prevUser = FIRAuth.auth()?.currentUser
FBSDKLoginManager().logInWithReadPermissions(["public_profile", "email"], fromViewController: self) { (result, error) in
if let token = result?.token?.tokenString {
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(token)
FIRAuth.auth()?.signInWithCredential(credential, completion: { (user, error) in
if user != nil && error == nil {
// Success
self.success?(user: user!)
dispatch_async(dispatch_get_main_queue(), {
self.dismissViewControllerAnimated(true, completion: nil)
})
}
})
}
}
}
Any pointers to remove the confusion would be great.
UPDATE:
I've realised I was confused about the app logic because of users being created during testing. Instead of 2 separate users being created for the above scenario (one authenticated via Facebook and another anonymously), all that happens is that the original anonymous user.uid "A" is "linked" to some Facebook authentication credentials. In the Firebase console this is shown by the anonymous uid changing from anonymous to one with the Facebook logo next to it.
This is what my working method looks like:
func signupWithFacebook(){
FBSDKLoginManager().logInWithReadPermissions(["public_profile", "email"], fromViewController: self) { (result, error) in
if let token = result?.token?.tokenString {
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(token)
FIRAuth.auth()?.currentUser!.linkWithCredential(credential) { (user, error) in
if user != nil && error == nil {
// Success
self.success?(user: user!)
dispatch_async(dispatch_get_main_queue(), {
self.dismissViewControllerAnimated(true, completion: nil)
})
} else {
print("linkWithCredential error:", error)
}
}
}
}
}
So your code follows the first 2 steps in this link. But the documentation explicity says not to call signInWithCredential but instead call
FIRAuth.auth()?.currentUser.linkWithCredential(credential) { (user, error) in
// ...
}
After getting your credential from Facebook's SDK.
Quote from link: "If the call to linkWithCredential:completion: succeeds, the user's new account can access the anonymous account's Firebase data."