I need to add to my current code, the necessary functionality and the exact code so that the user must verify the email before logging in.
Now, the user registers and automatically accesses all the functions of the application and its user panel. I want to add the necessary function so that when a user registers, a message is shown telling him that: You must verify your email In this way we ensure that it is a valid email and avoid the registration of SPA users.
I need the user to verify her email to be able to log in, until she does, she can continue using the App as she did, without logging in.
You can see that I did several tests, and other users tried to help me, but we have not achieved what is necessary, since I need to add the functionality to the code that I have now, since it is the only way I know to continue building my application.
The app has registration with Firebase, registered by email and password and I'm using Formik to control the state of the form and Yup to validate.
I have read Firebase documentation about "Send a verification message to a user",
This is the Firebase function:
```
const auth = getAuth();
sendEmailVerification(auth.currentUser)
.then(() => {
// Email verification sent!
// ...
})
```
The registration system I use now is Mail and Password. The user enters an email, a password, verifies the password and is automatically registered in the application.
I did several tests trying to add sendEmailVerification to my registration system, and for now what I have achieved is that the confirmation email arrives to the user (SPA folder) but the confirmation email arrives after the user already registered and use the app.
It would be necessary that the user could not register until receiving and confirming the "Confirmation Email"
I need a code example that fits my current app, I don't have the knowledge to change all my code, this is the base of my app.
What do I have to do so that this works correctly and the verification email arrives before the user can register?
What am I doing wrong in my code?
I show the application on GitHub, so they can see all the files
You can test the project as it is built with Expo:
exp://exp.host/#miguelitolaparra/restaurantes-5-estrellas?release-channel=default
This is the method I'm using to register users:
const formik = useFormik({
initialValues: initialValues(),
validationSchema: validationSchema(), // validate the form data
validateOnChange: false,
onSubmit: async(formValue) => {
try { // send the data to Firebase
const auth = getAuth()
// sendEmailVerification(auth.currentUser)
await createUserWithEmailAndPassword(
auth,
formValue.email,
formValue.password
)
sendEmailVerification(auth.currentUser)
navigation.navigate(screen.account.account)
} catch (error) {
// We use Toast to display errors to the user
Toast.show({
type: "error",
position: "bottom",
text1: "Failed to register, please try again later",
})
}
},
})
And I also show you the complete file:
import { useFormik } from 'formik'
import { getAuth, createUserWithEmailAndPassword, sendEmailVerification } from 'firebase/auth'
export function RegisterForm() {
const [showPassword, setShowPassword] = useState(false)
const [showRepeatPassword, setShowRepeatPassword] = useState(false)
const navigation = useNavigation()
const formik = useFormik({
initialValues: initialValues(),
validationSchema: validationSchema(), // validate the form data
validateOnChange: false,
onSubmit: async (formValue) => {
try { // send the data to Firebase
const auth = getAuth()
//sendEmailVerification(auth.currentUser)
await createUserWithEmailAndPassword(
auth,
formValue.email,
formValue.password
)
sendEmailVerification(auth.currentUser)
navigation.navigate(screen.account.account)
} catch (error) {
// We use Toast to display errors to the user
Toast.show({
type: "error",
position: "bottom",
text1: "Error al registrarse, intentelo mas tarde",
})
}
},
})
// function to hide or show the password
const showHidenPassword = () => setShowPassword((prevState) => !prevState)
const showHidenRepeatPassword = () => setShowRepeatPassword((prevState) => !prevState)
return (
// Registration form interface
<View>
<Input
placeholder="Correo electronico"
keyboardType="email-address"
containerStyle={AuthStyles.input}
rightIcon={
<Icon type="material-community" name="at" iconStyle={AuthStyles.icon} />
}
onChangeText={(text) => formik.setFieldValue("email", text)}
errorMessage={formik.errors.email}
/>
<Input
placeholder="Contraseña"
containerStyle={AuthStyles.input}
secureTextEntry={showPassword ? false : true}
rightIcon={
<Icon
type="material-community"
name={showPassword ? "eye-off-outline" : "eye-outline"}
iconStyle={AuthStyles.icon}
onPress={showHidenPassword}
/>
}
onChangeText={(text) => formik.setFieldValue("password", text)}
errorMessage={formik.errors.password}
/>
<Input
placeholder="Repetir contraseña"
containerStyle={AuthStyles.input}
secureTextEntry={showRepeatPassword ? false : true}
rightIcon={
<Icon
type="material-community"
name={showRepeatPassword ? "eye-off-outline" : "eye-outline"}
iconStyle={AuthStyles.icon}
onPress={showHidenRepeatPassword}
/>
}
onChangeText={(text) => formik.setFieldValue("repeatPassword", text)}
errorMessage={formik.errors.repeatPassword}
/>
<Button
title="REGISTRATE"
containerStyle={AuthStyles.btnContainer}
buttonStyle={AuthStyles.btn}
onPress={formik.handleSubmit} // send the form
loading={formik.isSubmitting}// show loading while doing user registration
/>
</View>
)
}
And this is the file to validate the form with Yup RegistreFormValidar.js
import * as Yup from "yup"
// object that has the elements of the form
export function initialValues() {
return {
email: "",
password: "",
repeatPassword: "",
}
}
// validate the form data whit Yup
export function validationSchema() {
return Yup.object({
email: Yup.string()
.email("El email no es correcto")
.required("El email es obligatorio"),
password: Yup.string().required("La contraseña es obligatoria"),
repeatPassword: Yup.string() // validate that the passwords are the same
.required("La contraseña es obligatoria")
.oneOf([Yup.ref("password")], "Las contraseñas tienen que ser iguales"),
})
}
You have several options to achieve your purpose.
First, to fix the SPA issue, you can use a custom domain, as shown on Firebase
To get what you are looking for, you can follow these steps:
1 - The user registers with an email address.
2 - The new record is created, but with status "To be verified" and an activation string is assigned.
3 - You send user data and activation string, along with a link to verify registration.
4 - The user clicks on the link, enters their data and, if they are valid, you change the status to "Active".
You can try to do it.
You also have the option to do it with "Authenticate with Firebase via email link"
For users to sign in via an email link, you must first enable the Email Provider and Email Link sign-in method for your Firebase project.
-Then send an authentication link to the user's email address.
To start the authentication process, show the user an interface that prompts them to enter their email address, then call sendSignInLinkToEmail to ask Firebase to send the authentication link to the user's email.
You can see all the details in the official Firebase documentation
1 - Build the ActionCodeSettings object, which provides Firebase with instructions to build the email link
const actionCodeSettings = {
// URL you want to redirect back to. The domain (www.example.com) for this
// URL must be in the authorized domains list in the Firebase Console.
url: 'https://www.example.com/finishSignUp?cartId=1234',
// This must be true.
handleCodeInApp: true,
iOS: {
bundleId: 'com.example.ios'
},
android: {
packageName: 'com.example.android',
installApp: true,
minimumVersion: '12'
},
dynamicLinkDomain: 'example.page.link'
};
2 - Ask the user for the email.
3 - Send the authentication link to the user's email and save their email in case the user completes the login with email on the same device
import { getAuth, sendSignInLinkToEmail } from "firebase/auth";
const auth = getAuth();
sendSignInLinkToEmail(auth, email, actionCodeSettings)
.then(() => {
// The link was successfully sent. Inform the user.
// Save the email locally so you don't need to ask the user for it again
// if they open the link on the same device.
window.localStorage.setItem('emailForSignIn', email);
// ...
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ...
});
Finally complete the access with the email link.
This is not exactly what you are looking for, but it may help.
Put this listener in your route You can tweak it according to your usage
useEffect(() => {
const unsubscribe = auth().onAuthStateChanged(
async (user) => {
if (user) {
if (user.emailVerified) {
store.setUser(user);
} else {
await user.sendEmailVerification();
auth()
.signOut()
.then(() => {
store.resetStore();
store.setAlertModal('Please Verify Your Email');
})
.catch((error) => log.error('Signout Error', error));
}
}
}
);
return () => {
// Unsubscribe
unsubscribe();
};
}, [store]);
As far as I understood, you need to verify email address of the user first, then create the user. Blocking functions maybe what you need.
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
return sendCustomVerificationEmail(user.email, link, locale);
});
}
});
This Firebase function will trigger before a new user is saved to the Firebase Authentication database, and before a token is returned to your client app. However, I think after this function executes, user is created. To prevent user creation you may have to implement a more complex flow.
One naive approach I can think of is as follows: After sending email to user, do not terminate the function and inside the function periodically check if user's email address is verified. Also set a timeout option and reject user creation after timeout. As expected, this approach increases the function execution time and can be costly.
If you are fine with the user being created in the Firebase Authentication database, I suggest implementing the solution stated in the documentation.
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
return sendCustomVerificationEmail(user.email, link, locale);
});
}
});
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
This will block users with unverified emails from logging into your app.
Check this documentation for other possible options: https://firebase.google.com/docs/auth/extend-with-blocking-functions#requiring_email_verification_on_registration
Related
I'm building a chat app with React and Firebase and I'm using functional React components. Please I would like to find out if there is a way to send a user an email if a message has not been read after one hour. I'm using triggerEmail to send emails when a property is created like this:
const formik = useFormik({
... //some other code here
onSubmit: async (values) => {
... //some other code here
await addDoc(collection(db, "mail"), {
to: values.email,
template: {
name: 'Property Uploaded',
data: {
id: values.id
}
}
})
}
})
This code runs when a property is created and an email is sent to the user as well as the admin. There's also a chat feature where users can send messages to the admin and this is wrapped in a custom react hook.
export default function useSendMessage() {
const [error, setError] = useState(null)
const { user } = useAuthContext()
const sendMessage = async (admin, agent, message) => {
setError(null)
try {
await addDoc(collection(db, "messages", admin.id, "messages"), {
admin: admin,
agent: agent,
isSender: admin.id === user.uid,
message: message.trim(),
markRead: false,
created_at: moment().format()
})
await addDoc(collection(db, "messages", agent.id, "messages"), {
admin: admin,
agent: agent,
isSender: agent.id === user.uid,
message: message.trim(),
markRead: false,
created_at: moment().format()
})
} catch (error) {
setError(error.message)
}
}
return { error, sendMessage }
}
And I'm using this hook in the chat app like this:
import useSendMessage from "../../hooks/useSendMessage";
export default function UserMessageInput({ admin, agent }) {
...//some code here
const handleSubmit = async (e) => {
e.preventDefault()
setSending(true)
setMessage("")
await sendMessage(admin, agent, message)
.then(() => {
setSending(false)
})
}
return(
...//chat app UI
)
}
Is there a way to trigger the email service if markRead is false after one hour? I would like to notify the admin or the agent that they have a new message on the site. I'm not sure setTimeout or setInterval can work because the agent or the admin might be offline at certain times.
You can schedule a Cloud Function to run in exactly one hour after the message doc has been created, as explained in this article titled "How to schedule a Cloud Function to run in the future with Cloud Tasks (to build a Firestore document TTL)".
Concretely, in the HTTP callback function that is invoked by Cloud Tasks you will first check the value of the markRead boolean field in the Firestore document and, if it is false, you'll send the email by creating a doc in the mail collection, since you use the Email extension.
Another approach would be to use a scheduled Cloud Function to run e.g. every minute, checking is the message was create more than one hour ago and is not marked as read. The above referred article explains the drawbacks of this approach.
I want to implement role based authentication in my app.
After researching for a while I found a simple solution - to pass role to signIn function
signIn('email', { email, role: 'user' });
Then I can unpack it from req's body
import NextAuth from 'next-auth'
export default async function auth(req, res) {
return NextAuth(req, res, {
// providers, adapters, etc.
callbacks: {
signIn: async ({ user }) => {
const { body: { role } } = req;
if (role) {
user.role = role;
}
return true;
},
session: async ({ user, session }) => {
session.user = {...session.user, ...user };
return session
}
}
}
});
Or so I thought. Magic link sign in flow has two steps: sending email and confirming it.
The role I provide to signIn function is only available during the first step and saving user to users collection only happens after user confirms email, so when user confirms email there's no role.
I tried storing role in cookies
if (role) res.setHeader('Set-Cookie', 'user-role=' + role + ';Max-Age=600');
But cookies get overriden after I confirm email and the role is lost.
I don't want to create additional collections and/or records in my database.
How can I preserve role inside signIn callback without storing it in a separate database collection? Maybe there's some other solution you can think of?
I am working for a task related to react native app based user management. After they successful sign up themselves as a user, I include a data which is store Boolean value and I have named it as emailVerifiedAccount (this is the stored data if they registered themselves successful and indicate if they are a verified user if they click on email verification link send to them). In login screen I would like to check the Boolean value that I get from firestore in onAuthStateChanged and only direct them to index screen when the emailVerifiedAccount that I get is return true. Code below is authstatechanged for login screen .
const onAuthStateChanged = (user) => {
if (user) {
checkEmailVerifiedAccount=firestoreService.isEmailVerifiedAccount(user);
try {
if(checkEmailVerifiedAccount===true){
navigation.navigate('Index');
}else{
Alert.alert(
"Verify as a Playbookx user",
"Please click on email verification link send to your email. If you do not receive any email ,please contact user support",
"You may come back to login again after you verified your email",
[
{ text: "OK"}
]
)
}
} catch (error) {
console.log(error);
}
}
};
and in firestore.js is where all the code and function that is related with Firestore database. isEmailVerifiedAccount is one of the class object which checking verified user using email.
isEmailVerifiedAccount = async (user) => {
return await firestore()
.collection('users')
.doc(user)
.get()
.then(snapshot => {
if (snapshot.exists){
const user=snapshot.data();
const emailVerifiedAccount={
emailVerifiedAccount:user.emailVerifiedAccount
};
return emailVerifiedAccount
}
})
.catch(error => {
console.log(error);
});
};
And the problem that I facing right now is that, it lead me to index screen when the emailVerifiedAccount in Firestore is false. Picture below is the structure for Firestore.
Your isEmailVerifiedAccount function is asynchronous, so when calling it you have to await the result:
checkEmailVerifiedAccount = await firestoreService.isEmailVerifiedAccount(user);
const checkEmailVerifiedAccount = firestoreService.isEmailVerifiedAccount(user);
checkEmailVerifiedAcccount seems to be an object but you are checking if it's equal to true. Either return user.emailVerifiedAccount directly from isEmailVerifiedAccount function or read that property like this:
const checkEmailVerifiedAccount = firestoreService.isEmailVerifiedAccount(user);
if(checkEmailVerifiedAccount.emailVerifiedAccount) {
navigation.navigate('Index');
}
am working on a little project and i did finish all the authentication work but one thing,am wondering how to check if the email is real before going into the process of signup,
by the way am using react and Firebase and i did look online and i did find a package called email-existence i did try it and it dose return true if the email is real and false if the email dosent exist but thats not working when i use it with react it return an error
import firebase from '../util/firebase';
const emailExistence = require('email-existence');
export const normalSignup = (props, setSign, email, password, confirmPassword, username) => {
emailExistence.check(email, function (error, response) { // return error here addresses.sort is not a function
console.log('res: ' + response);
});
}
anyway am wondering if there's a way to do it with Firebase without external packages thanx in advance
PS:am not using cloud functions
Well assuming you want to check if the email is a verified email address you can write the code in the following way
import firebase from '../util/firebase';
const App = {
firebase: firebase,
getLoggedInUser: () => {
const currentUser = App.firebase.auth().currentUser
if (currentUser) {
return {
email: currentUser.email,
userId: currentUser.uid,
isEmailVerified: currentUser.emailVerified
}
} else {
return undefined
}
},
isAuthenticated: () => {
return (App.getLoggedInUser() && App.getLoggedInUser().isEmailVerified)
},
authenticate: async (email, password) => {
await App.firebase.auth().signInWithEmailAndPassword(email, password)
},
signup: async (email, password) => {
const userCredential = await App.firebase.auth().createUserWithEmailAndPassword(email, password)
await userCredential.user.sendEmailVerification()
return `Check your email for verification mail before logging in`
},
Here the following happens
When a user signs up the signup method is called and an email verification is sent by firebase as shown in the above code
When a user logs in the authenticate method is called so according to firebase you are logged in
However to redirect or render a certain page say after log in you can use the isAuthenticated method to display a page to a certain user
So you can pass method isAuthenticated as a prop to react-router and render your web application how you want.
This way only real and authentic email id which are verified will have access to your app
Note
This method is working already in prod but its using VueJS and is an opensource project on github let me know if you want to reference it
Maybe just use a regex to check if the email is valid?
According to this webpage for JavaScript you just need:
const emailRegex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (emailRegex.test(email)) {
console.log('Email valid!');
}
This won't stop people entering emails for incorrect domains, but ensures that if someone uses a mail server that isn't widely known, it will get accepted too.
Your only option on the client side (if you are on Firebase I suppose you don't have the luxury to run a Node backend) to fetch a similar service as email-existence which returns a "valid" or "invalid" response if you GET the endpoint with the email address.
These are usually premium services, but if you have low traffic you can try out a free one. In my example it is Mailboxlayer.
Their endpoint can be called like this (and of course if you are stick to the client side it means anyone can steal your api key from production via browser network tab!):
GET http://apilayer.net/api/check?access_key=YOUR_ACCESS_KEY&email=richard#example.com
Which returns a JSON:
{
"email": "richard#example.com",
"did_you_mean": "",
"user": "support",
"domain": "apilayer.net",
"format_valid": true,
"mx_found": true,
"smtp_check": true,
"catch_all": false,
"role": true,
"disposable": false,
"free": false,
"score": 0.8
}
Best to use score, which:
[...] returns a numeric score between 0 and 1 reflecting the quality and deliverability of the requested email address.
In React:
const [data, setData] = useState(null)
const [emailToVerify, setEmailToVerify] = useState('richard#example.com') // just for the sake of example
const apiKey = process.env.API_KEY
const fetchEmailVerificationApi = useCallback(async () => {
try {
const response = await fetch(`http://apilayer.net/api/check?access_key=${apiKey}&email=${emailToVerify}`)
const json = await response.json()
setData(json.score) // returns a numeric score between 0 and 1 reflecting the quality and deliverability of the requested email address.
} catch (e) {
console.error(e)
}
}, [apiKey, emailToVerify])
useEffect(() => {
fetchEmailVerificationApi()
}, [fetchEmailVerificationApi])
I am trying to change/update a user's email address using :
firebase.auth().changeEmail({oldEmail, newEmail, password}, cb)
But I am getting ...changeEmail is not a function error. I found the reference here from the old firebase docu.
So how to I do it in the 3.x version? Because I cant find a reference in the new documentation.
You're looking for the updateEmail() method on the firebase.User object: https://firebase.google.com/docs/reference/js/firebase.User#updateEmail
Since this is on the user object, your user will already have to be signed in. Hence it only requires the password.
Simple usage:
firebase.auth()
.signInWithEmailAndPassword('you#domain.example', 'correcthorsebatterystaple')
.then(function(userCredential) {
userCredential.user.updateEmail('newyou#domain.example')
})
If someone is looking for updating a user's email via Firebase Admin, it's documented over here and can be performed with:
admin.auth().updateUser(uid, {
email: "modifiedUser#example.com"
});
FOR FIREBASE V9 (modular) USERS:
The accepted answer will not apply to you. Instead, you can do this, i.e., import { updateEmail } and use it like any other import. The following code was copy/pasted directly from the fb docs at https://firebase.google.com/docs/auth/web/manage-users
Happy coding!
import { getAuth, updateEmail } from "firebase/auth";
const auth = getAuth();
updateEmail(auth.currentUser, "user#example.com").then(() => {
// Email updated!
// ...
}).catch((error) => {
// An error occurred
// ...
});
You can do this directly with AngularFire2, you just need to add "currentUser" to your path.
this.af.auth.currentUser.updateEmail(email)
.then(() => {
...
});
You will also need to reauthenticate the login prior to calling this as Firebase requires a fresh authentication to perform certain account functions such as deleting the account, changing the email or the password.
For the project I just implemented this on, I just included the login as part of the change password/email forms and then called "signInWithEmailAndPassword" just prior to the "updateEmail" call.
To update the password just do the following:
this.af.auth.currentUser.updatePassword(password)
.then(() => {
...
});
updateEmail needs to happen right after sign in due to email being a security sensitive info
Example for Kotlin
// need to sign user in immediately before updating the email
auth.signInWithEmailAndPassword("currentEmail","currentPassword")
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success now update email
auth.currentUser!!.updateEmail(newEmail)
.addOnCompleteListener{ task ->
if (task.isSuccessful) {
// email update completed
}else{
// email update failed
}
}
} else {
// sign in failed
}
}
async updateEmail() {
const auth = firebase.auth();
try {
const usercred = await auth.currentUser.updateEmail(this.email.value);
console.log('Email updated!!')
} catch(err) {
console.log(err)
}
}
You can use this to update email with Firebase.
Firebase v9:
const changeEmail = (userInput) => {
const { newEmail, pass } = userInput
signInWithEmailAndPassword(auth, oldEmail, pass)
.then(cred => updateEmail(cred.user, newEmail))
}