how to send verification email to users using Firebase Auth - firebase

I want to send verification email to users who just signed up. I have been looking for a solution for this and thought I found one, but it didn't work.
I checked out the Firebase Auth document and tried code on the page, but it also couldn't be the solution.
My code is below
The "try" actually runs when pressing the sign-up button and I can see it.
In the Firebase Auth console page, I can see the user just created but didn't get a verification email in the inbox.
const handleSignup = async (email, pswd) => {
try {
await createUserWithEmailAndPassword(auth, email, pswd);
setSignupEmail("");
setSignupPassword("");
setSignupPasswordAgain("");
signOut(auth);
sendEmailVerification(auth.currentUser);
alert("Check your email for a verification link.");
} catch (err) {
console.log(err);
}
};

You call sendEmailVerification() after having called signOut() so auth.currentUser is null when you pass it to sendEmailVerification(). You should see an error logged in the catch block.
Moreover, both sendEmailVerification() and signOut() methods are asynchronous and return Promises.
The following should do the trick (untested):
const handleSignup = async (email, pswd) => {
try {
await createUserWithEmailAndPassword(auth, email, pswd);
setSignupEmail("");
setSignupPassword("");
setSignupPasswordAgain("");
await sendEmailVerification(auth.currentUser);
await signOut(auth);
alert("Check your email for a verification link.");
} catch (err) {
console.log(err);
}
};

Related

How to allow username/password and email/password login?

I am trying to make my sign-up process more simple for users by not requiring an email. When that user goes to log in again, there are three situations.
They didn't sign up with an email, and I set their email as username#mydomain.com on the backend. They log in with their username/password, and I append #mydomain.com to their username before authenticating.
They did sign up with an email, and they use their email/password to log in.
They did sign up with an email, but they log in with their username and password.
For situation 3, I can't figure out a good way to authenticate that user. My initial idea was to send the username and password to a cloud function with the intent of matching the username to its email and verifying the password, but it doesn't seem like the admin SDK has a way to do this. I could of course send the email back to the client, but that seems unsecure. Am I missing something/any ideas here? Thank you.
edit.
I was finally able to implement what Dharmaraj suggested. See below. Hopefully someone else finds this useful!
// The Cloud Functions for Firebase SDK to
// create Cloud Functions and set up triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
const axios = require('axios');
const apiKey = 'API KEY';
const signInURL = 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=' + apiKey;
exports.getEmail = functions.https.onCall(async (data, context) => {
// Grab the text parameter.
const username = data.text.toLowerCase();
const password = data.password;
const uidData = await admin
.firestore()
.collection('usernameToUid')
.doc(username)
.get();
const uid = uidData.get('uid');
if (uid == null) {
return {result: 'auth/user-not-found'};
} else {
const emailData = await admin
.firestore()
.collection('emails')
.doc(uidData.get('uid'))
.get();
const email = emailData.get('email');
if (email == null) {
return {result: 'auth/email-not-found'};
} else {
// try {
// const response = await axios
// .post('https://rapidapi.com/learn/api/rest', {
// name: 'John Doe',
// });
// return {result: JSON.stringify(response.data)};
// } catch (error) {
// return {result: error.message};
// }
try {
const response = await axios
.post(signInURL, {
email: email,
password: password,
returnSecureToken: true,
});
return {result: response.data.email};
} catch (error) {
return {result: 'auth/incorrect-password'};
}
}
}
});
There isn't any way to verify user's password using Admin SDk but a workaround would be to use Firebase Auth REST API with Cloud Function. Instead of returning email to the client side, after fetching email by username from database, you can impersonate user login.
exports.verifyPassword = functions.https.onCall((data, context) => {
const { username, password } = data;
const email = getEmailByUsername(username);
// TODO: user Firebase Auth REST API
});
You can use fetchSignInMethodsForEmail to check if username#mydomain.com exists. If it does not, then check for custom user email domains using Cloud Functions as mentioned before.

Firebase Auth: How to unsubscribe from Auth observer after user creation and then subscribe again?

I am using the createUserWithEmailAndPassword() method for signing up new users. Immediately after this user creation process, I am sending an email verification. Then, in my onAuthStateChanged() I have a condition to check whether the user has verified their email. The problem is that the Auth observer is logging out the user BEFORE the email sendEmailVerification() method is complete.
Based on the below code, where is the best place to succuessfully unsubscribe the observer ? And, how to do it with Firebase JS SDK v9?
Let me explain my use case and show my code:
pages/sign-up:
async signUp() {
const auth = getAuth()
const batch = writeBatch(db)
try {
const UserCredential = await createUserWithEmailAndPassword(
auth,
this.formValues.email,
this.formValues.password
)
const userDocRef = doc(db, 'users', UserCredential.user.uid)
batch.set(userDocRef, {
uid: UserCredential.user.uid,
displayName: this.formValues.displayName,
photoURL: `https://gravatar.com/avatar/${md5(
this.formValues.email
)}?d=identicon`
})
const usernameDocRef = doc(db, 'usernames', this.formValues.displayName)
batch.set(usernameDocRef, { uid: UserCredential.user.uid })
// Commit batch
await batch.commit()
console.log('batch committed, user is:', UserCredential.user.uid)
await this.verifyEmail() // <-- user is logged out before this has a chance to fire!
verifyEmail():
async verifyEmail() {
const auth = getAuth()
const actionCodeSettings = {
url: `${this.$config.baseUrl}/email-confirmation/success`
}
try {
await sendEmailVerification(auth.currentUser, actionCodeSettings)
} catch (error) {
console.error('An email verification error happened', error)
this.errorMessage = error.message
}
},
In my onAuthStateChanged() method, I am immediately logging out the user IF their email is not yet verified. This causes the following error:
And here is how I have my onAuthStateChanged observer set up (it runs before the page is rendered):
~/plugins/auth.js:
onAuthStateChanged(auth, (user) => {
if (user) {
if (!user.emailVerified) {
// User has not verified the email yet
store.dispatch('logOutUser')
}
// TO DO: finish up rest of user logic
Should the unsubscribe be in the auth.js or the pages/sign-up page? I am unsure how to unsubscribe.
If you need to perform certain actions after signup/login, then you should unsubscribe from auth observer as you've figured out.
const authObserver = onAuthStateChanged(auth, (user) => {
// ...
}
async signUp() {
//unsubscribe here i.e when user clicks signup button
authObserver()
const auth = getAuth()
const batch = writeBatch(db)
// ...
}
Do note that, if you you auth observer is meant to redirect logged in user somewhere else then it won't do it now. So make sure you do that manually.

firebase Sign in with email and password not working with await function vuejs

async signIn() {
try {
let userCredential= await firebase.auth().signInWithEmailAndPassword(this.email, this.password)
console.log("user value is",userCredential)
this.$router.replace({name:'Sidebar'})
} catch (err) {
console.log("Invalid user", err);
}
},
},
};
When I use login without the await function it logs in, even on wrong credentials, and if I use the await function nothing happens and also it shows no errors.
When using the await function even on wrong credentials it's not showing an error, and also on correct credentials, it's not showing console.log value. What am I doing wrong here? why is it not checking DB for email and password!
You should dont use try catch here, make it simple like this. Beside you should not post information about firebase. It is sensitive infor.
firebase.auth().signInWithEmailAndPassword(this.email, this.password)
.then(function(firebaseUser) {
// Success
console.log("user value is", firebaseUser)
this.$router.replace({name:'Sidebar'})
})
.catch(function(error) {
// Error Handling
console.log("Invalid user", error);
});
I found the error. The form was not submitting the correct value at submission. Had to render the value outside the form and pass the values normally without form.

Firebase + Flutter: can't lock access to unverified email accounts

I'd like to block out people who didn't verify their email so i figured out this code for sign up:
// sign up
Future signUp(String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} catch (e) {
print('An error has occured by creating a new user');
print(
e.toString(),
);
}
try {
final FirebaseUser _user = await _auth.currentUser();
await _user.sendEmailVerification();
} catch (error) {
print("An error occured while trying to send email verification");
print(error.toString());
}
try {
await _auth.signOut();
} catch (err) {
print(err);
}
}
and this for sign in:
//Sign In with Email and Pass
Future signInWithEmailAndPassword(String email, String password) async {
FirebaseUser _user = await FirebaseAuth.instance.currentUser();
if (_user != null && _user.isEmailVerified == true) {
try {
await _auth.signInWithEmailAndPassword(
email: email, password: password);
return _user;
} catch (e) {
return null;
}
} else {
return null;
}
}
_auth is just an instance of FirebaseAuth.
The problem is that i can login even if i didnt verify the email.
Firebase Auth doesn't stop accounts from signing in if the user hasn't verified their email address yet. You can check that property _user.isEmailVerified to find out the state of that validation after the user signs in, and you can determine from there what the user should see.
isEmailVerified can be a little bit of trouble to get working correctly.
Make sure you are calling
await FirebaseAuth.instance.currentUser()..reload();
before your are calling isEmailVerified also in my own experience and I don't know if this is just something I was doing wrong but this did not work from my Auth class this did not start working until I put the code directly in initState() of my widget that checks whether the user is verified. Like I said that part might have been something I did wrong. Like stated this will not listen for change you must check yourself either periodically or at a point that you know email is verified.
Future(() async {
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
await FirebaseAuth.instance.currentUser()
..reload();
var user = await FirebaseAuth.instance.currentUser();
if (user.isEmailVerified) {
timer.cancel();
Navigator.of(context).popAndPushNamed(HearingsScreen.routeName);
}
});
});
So it checks every 10 seconds to see if the user has verified their email not the most elegant solution. The page I have this on just displays a message 'Please verify your email' so its not like this is interrupting other code. If your app is performing other tasks this might not be an option for you. If you want to play around with isEmailVerified go ahead but i spent a week of headaches until i settled on this.

Firebase confirmation email not being sent

I've set up Firebase email/password authentication successfully, but for security reasons I want the user to confirm her/his email.
It says on Firebases website:
When a user signs up using an email address and password, a confirmation email is sent to verify their email address.
But when I sign up, I doesn't receive a confirmation email.
I've looked and can only find a code for sending the password reset email, but not a code for sending the email confirmation.
I've looked here:
https://firebase.google.com/docs/auth/ios/manage-users#send_a_password_reset_email
anyone got a clue about how I can do it?
I noticed that the new Firebase email authentication docs is not properly documented.
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
});
Do note that:
You can only send email verification to users object whom you created using Email&Password method createUserWithEmailAndPassword
Only after you signed users into authenticated state, Firebase will return a promise of the auth object.
The old onAuth method has been changed to onAuthStateChanged.
To check if email is verified:
firebase.auth().onAuthStateChanged(function(user) {
if (user.emailVerified) {
console.log('Email is verified');
}
else {
console.log('Email is not verified');
}
});
After creating a user a User object is returned, where you can check if the user's email has been verified or not.
When a user has not been verified you can trigger the sendEmailVerification method on the user object itself.
firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then(function(user){
if(user && user.emailVerified === false){
user.sendEmailVerification().then(function(){
console.log("email verification sent to user");
});
}
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode, errorMessage);
});
You can also check by listening to the AuthState, the problem with the following method is, that with each new session (by refreshing the page),
a new email is sent.
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
});
The confirmation email could be in your spam folder.
Check your spam folder.
You can send verification email and check if was verified as follow into the AuthListener:
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
//---- HERE YOU CHECK IF EMAIL IS VERIFIED
if (user.isEmailVerified()) {
Toast.makeText(LoginActivity.this,"You are in =)",Toast.LENGTH_LONG).show();
}
else {
//---- HERE YOU SEND THE EMAIL
user.sendEmailVerification();
Toast.makeText(LoginActivity.this,"Check your email first...",Toast.LENGTH_LONG).show();
}
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
// [START_EXCLUDE]
updateUI(user);
// [END_EXCLUDE]
}
};
if you're using compile "com.google.firebase:firebase-auth:9.2.0" and
compile 'com.google.firebase:firebase-core:9.2.0' the method sendEmailVerification() will not be resolved until you update to 9.8.0 or higher. It wasted most of time before I figured it out.
I have been looking at this too. It seems like firebase have changed the way you send the verification. for me
user.sendEmailVerification()
did not work.
If you get an error such as user.sendEmailVerification() doesn't exist.
use the following.
firebase.auth().currentUser.sendEmailVerification()
It's not the answer to the question but might help someone.
Don't forget to add your site domain to the Authorised domains list under Sign-in-method
You could send a verification email to any user whose email is linked to the Firebase Auth account. For example, in Flutter you could do. something like :
Future<void> signInWithCredentialAndLinkDetails(AuthCredential authCredential,
String email, String password) async {
// Here authCredential is from Phone Auth
_auth.signInWithCredential(authCredential).then((authResult) async {
if (authResult.user != null) {
var emailAuthCredential = EmailAuthProvider.getCredential(
email: email,
password: password,
);
authResult.user
.linkWithCredential(emailAuthCredential)
.then((authResult,onError:(){/* Error Logic */}) async {
if (authResult.user != null) {
await authResult.user.sendEmailVerification().then((_) {
debugPrint('verification email send');
}, onError: () {
debugPrint('email verification failed.');
});
}
});
}
});
}

Resources