Firebase sendEmailVerification prior to createUser - swift

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.

Related

How can you add a user phone number in the same child as email?

Use Case: App build around email (with three childs of importance here). Now I just want cell phone login users to have the same Childs.
So something like this below must not produce an error(it currently does). I can do everything else with phone login (even create a user with phone number), but I want him to have these Childs too, for when the user uses phone and not email.
Auth.auth().createUser(withEmail: ResultString, password: remainingPart) { (user, error) in
let databaseRef = Database.database().reference()
guard error == nil else { return }
guard let user = user else { return }
let userObject =
[
"users": ResultString,
"postID": user.user.uid,
"e2": remainingPart,
] as [String: Any]
databaseRef.child("people").child(user.user.uid).setValue(userObject)
print("YESSSSS")
}
It seems that you're trying to pass a phone number to createUser(withEmail:,password:). Since a phone number is not a valid email address, the API rejects it.
To sign a user in with their phone number, follow the documentation for phone number sign-in.
After singing the user in with their phone number, you can still write their details to the database, same as you're doing now.

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.

Swift 5 & Firebase: Creating a shared sign in and sign-out button

I thought I'd return to StackOverflow with another question because you guys helped me significantly with my last issue
Anyway, I currently have my authentication system setup so that the sign in and signup button are shared. I am looking to have firebase reference storage when an email is entered to have it checked against other accounts in the database. As of right now, a user can enter an email address for their account and then if they enter the wrong password it just sends them right to the sign up even though they currently have an account. This is a serious problem as it will cause confusion
I want it to work like so:
If the email address is taken, I want an alert to be displayed for the user says "Incorrect password"
If the email address is not taken, I want it to tell the user that they need to enter a password with at least 10 characters, 1 number, and 1 special character, which I have already figured out using
I only want it to segue to create a new user if the email is not taken and the password and email field meet the criteria fields that I have already set within my code. I just need help preventing it from switching to the create new user VC if the email is already taken, and I need to to say
func isValidPassword(_ email: String) -> Bool {
let emailRegEx = "##$%^&+=^.*(?=.{10,})(?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[##$%^&+=]).*$"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
Anyway, here is the code so far attached to the IBAction
if let email = emailField.text, let password = passwordField.text {
Auth.auth().signIn(withEmail: email, password: password, completion:
{ (user,error) in
if error == nil {
if let user = user {
self.userUid = user.user.uid
self.goToFeedVC()
}
} else {
self.goToCreateUserVC()
Here is a picture of the interface
I want it to be intuitive but I have been unable to code this myself so if anyone is able to help advise me on how to finish this block it would be incredibly appreciated
Firebase gives pretty detailed error responses for their Auth call:
So you can check to see what the error is inside of your call:
Below are the two error that they give (I only added the two scenarios that you mentioned)
Description: The password is invalid or the user does not have a password.
FIRAuthErrorUserInfoNameKey: ERROR_WRONG_PASSWORD
&
There is no user record corresponding to this identifier. The user may have been deleted.
FIRAuthErrorUserInfoNameKey: ERROR_USER_NOT_FOUND
Auth.auth().signIn(withEmail: email, password: password, completion:
{ (user,error) in
if error == nil {
if let user = user {
self.userUid = user.user.uid
self.goToFeedVC()
}
} else {
guard let error = error?.localizedDescription else { return } // but actually handle this
print(error)
if error == wrong password {
// show alert for email taken/wrong password
} else if error == user doesnt exists {
// self.goToCreateUserVC()
}
}
}
Just replace the if and else if conditions with the actual errors. I'd avoid comparing the strings and use the key/code in case the strings change in the future.
Official list of error codes can be found here
And if you print the full error instead of the error?.localizedDescription you'll get the full details, as can be seen here:
Optional(Error Domain=FIRAuthErrorDomain Code=17011 "There is no user record corresponding to this identifier. The user may have been deleted." UserInfo={NSLocalizedDescription=There is no user record corresponding to this identifier. The user may have been deleted., FIRAuthErrorUserInfoNameKey=ERROR_USER_NOT_FOUND})

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

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

compare textfield.text to firebase string swift

I have a database in Firebase that will have individual user nodes. In each user's node will be data pertaining to them and will be private. In addition to that I want to create a node that is JUST a collection of registered emails. The reason is when a user is on the Sign In VC and the user types an email in..If the email is already registered an image view will turn green. However, if the email is not in the database (or if it doesn't match email address format) the image will be red.
A previous answer on my previous question(s) illustrated that I need to change the '.' to a ',' in email addresses. So #gmail.com would be stored as gmail,com
I have that down.
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
if error == nil {
let email = firstContainerTextField.text ?? ""
let newString = email.replacingOccurrences(of: ".", with: ",", options: .literal, range: nil)
self.ref.child("users").child("allUsers").child(newString).setValue(true)
self.ref.child("users").child((user?.uid)!).setValue(["Email": email])
FIRAuth.auth()!.signIn(withEmail: email,
password: password)
} else {
//registration failure
}
This is the code from the New User VC (partial).
So the node that says "users" and "allUsers" looks like this on Firebase Console
users
allUsers
bob#bob,com: true
ted#ted,com: true
The 'true' part was just so I could get the bob#bob,com onto the database...the true part will never be used for anything.
On the log in VC I honestly cannot figure out what to do
A previous answer said to use
hasChildren()
And I used that and then googled what to do with that
and I tried using something like this
ref.child("users").child("allUsers").queryEqual(toValue: newString)
.observe(.value, with: { snapshot in
if snapshot.hasChildren() {
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
....
}
});
But I just cannot seem to get anywhere with it.
How can I simply see if a textfield.text == an email already stored in firebase?
(I did convert the '.' to ',' when comparing)
Please don't use email addresses as keys. Email addresses are dynamic and may change (as in if the users wants to change it) and if they do, you'll have a mess on your hands as every node that directly used that email would have be deleted and re-created.
Best practice is to disassociate key's from the data they contain.
Here's the structure to use
emails
-Yiaisjpa90is
email: "dude#test.com"
-Yijs9a9js09a
email: "thing#test.com"
then you simply query the email node for the email you are looking for, and handle accordingly if it exists.
And some code
emailsRef.queryOrdered(byChild: "email").queryEqual(toValue: "dude#test.com")
.observe(.value, with: { snapshot in
if snapshot.value is NSNull {
print("the snapshot was null, no email found")
} else {
print("email was found, YIPEE")
}
})
For completeness it would be a little more Swifty to use
if snapshot.exists() {
print("found it")
} else {
print("no email found")
}