11

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

enter image description here

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"),
  })
}
Miguel Espeso
  • 194
  • 1
  • 7
  • 26
  • 2
    Some sendmail servers support `VRFY` that lets you check the validity of an email address without sending email, but that's not universal. Also confirm that you can send any email at all – Barry Carter Aug 01 '22 at 15:13
  • Thanks for your contribution @barry , Unfortunately, my experience and I don't have much skills, so I use Firebase although the experience is also short and I can't understand it. The sendEmailVerification method is very simple and functional, but I can't place it correctly in my code – Miguel Espeso Aug 01 '22 at 15:25
  • 1
    "Is there another method to verify that the Email is correct other than Sending a verification message to a user?" Can you clarify what you have in mind there? I understand what you *don't* want to do, but how **do** you then expect an email verification mechanism to work? – Frank van Puffelen Aug 01 '22 at 15:27
  • 2
    "the confirmation message does not arrive in his email" This most likely means it's being marked as spam, either on their system or before it even reaches that. Have the user's check their spam folder, and see https://stackoverflow.com/questions/72922475/why-did-this-code-fail-to-send-password-reset-link-in-firebase-reactjs/72922603#72922603 – Frank van Puffelen Aug 01 '22 at 15:28
  • 1
    OK, just try sending any email to yourself just to make sure you have "email sending" enabled in Firebase – Barry Carter Aug 01 '22 at 15:29
  • Indeed, the verification email arrived in the Spam folder, @frankVanPuffelen (I will correct this later) I have changed this line of the site: sendEmailVerification(auth.currentUser) . But despite arriving in the spam folder, the user registers at this time, without waiting for us to verify the email. In this way, anyone can register with a false email, since by touching the "REGISTER" button, the user logs in to the App. Something is wrong The correct thing would be that you could not log in until you have verified the mail – Miguel Espeso Aug 01 '22 at 15:50
  • 1
    In order to send an email to a user, that user has to be signed in to Firebase Authentication. Whether you allow anyone who is signed in to use your app and access data, is up to you though and is specific to each app (plenty of apps don't require email verification, so Firebase can't require this on an API level). If you want to only allow them to do so *after* they verified their email address, you can check for that in their token/profile in the client-side code, in any server-side code you have, and in the security rules of your database and storage. – Frank van Puffelen Aug 01 '22 at 16:12
  • If your app requires a verified email to sign in though, consider using the email link verification method that combines the email verification with signin in and removes the password requirement (although you can still add that if you want): https://firebase.google.com/docs/auth/web/email-link-auth – Frank van Puffelen Aug 01 '22 at 16:14
  • Of course, the intention is that the user can log in when they have verified their mail. Otherwise, anyone can access the app even if they write a fake email. That's what I need, and what I thought was happening with the sendEmailVerification function. Should I modify my question? How can I achieve this with the code I already have written? – Miguel Espeso Aug 01 '22 at 16:19
  • 1
    try email link authuntication, after varifying email you can easily tell user to set password https://firebase.google.com/docs/auth/web/email-link-auth – Salman Shaikh Aug 05 '22 at 12:42
  • But I already have the whole application around this mail and password authentication. I already tried what you are proposing @salmanShaikh , but the application is built around this, now I wouldn't know how to modify my application to achieve this. I have created the App following a course, I still need to learn a lot. Actually, at this point I won't be able to modify the App to achieve this. In the future sure yes, but not at the moment – Miguel Espeso Aug 05 '22 at 13:55
  • 1
    The logged-in user object has an `emailVerified` variable. Is it possible to use that value as a guard? – Mahesh Samudra Aug 31 '22 at 20:18
  • You can do whatever you think is necessary to make it work. I don't know how to do it, and I don't know what to do either, I'm very frustrated, – Miguel Espeso Aug 31 '22 at 21:34

3 Answers3

3

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.

0

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]);
Bhavya Koshiya
  • 1,262
  • 2
  • 8
  • 22
  • Where should I put this exactly friend @BhavyaKoshiya ? I don't know where to put it in my code – Miguel Espeso Sep 05 '22 at 15:38
  • 1
    in your routing class or function where your navigation container is – Bhavya Koshiya Sep 06 '22 at 03:49
  • I would be grateful if you help me. [I show him the app on GitHub](https://github.com/miguelitolaparra/bussines) so he can tell me where to add this code snippet. I've done several tests and I haven't been able to get it to work. The bounty is about to end. can we get another – Miguel Espeso Sep 06 '22 at 10:28
  • 1
    inside your AppNavigation.js – Bhavya Koshiya Sep 06 '22 at 11:27
  • I've added your code after this line: `export function AppNavigation() {...` But I receive several errors, including the `"store"` variable that I don't know how to correct. I also get errors for TypeScript syntax, I don't use `TypeScript` – Miguel Espeso Sep 06 '22 at 12:37
  • i have updated it to javascript and store is for my app which uses mobx stores you need to place your custom logic in that for both cases if user is verified and if not – Bhavya Koshiya Sep 07 '22 at 03:34
  • Well friend @BhavyaKoshiya , I am very grateful for your support, but I have spent 5 days trying to add your example to my application and I have not been able to get it to work. I don't know what is the correct way to do it, my experience is short. I'll keep waiting for a good idea or to have more experience and understand better how Firebase works. Thanks for taking your time, I'm sorry you weren't given the reward – Miguel Espeso Sep 12 '22 at 11:02
  • 1
    No problem dude, just sad that was not able to help you enough – Bhavya Koshiya Sep 12 '22 at 11:18
  • No, if you have helped me, the problem is mine, that I do not have the necessary knowledge in this matter to be able to add your function to my App. Maybe someone will see my repository on GitHub and be able to solve this. You can rest assured, you have done everything you can to help me. Thanks. – Miguel Espeso Sep 12 '22 at 11:25
-1

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

AyseAsude
  • 554
  • 4
  • 13
  • Thank you for the trouble you took to prepare your answer @ayseasude , it really is very good. The problem is that I don't know how or where I can add your example to my code. Can you give me more details of where I should place this ? Thanks, at the moment I can't accept your answer, I've read this too, but I don't know how to implement it in my file without breaking it too much – Miguel Espeso Aug 05 '22 at 13:58
  • 1
    This piece of code is deployed with Firebase Functions so it is not on the client side. If you want to use these functions your project must be on Blaze plan and you must activate Firebase Authentication with Identity Platform. After deploying your function to the cloud, when a user signs in, it automatically runs (triggers). – AyseAsude Aug 05 '22 at 14:07
  • I think this is not the most suitable for me, I'm sorry, as you can see, to offer the reward I said that I needed a detailed and functional answer. What you tell me I have already read, and anyone can add an answer like the one you have given. I'm sorry, I can't accept it. Thank you for your dedication @ayseasude And of course my app doesn't need a Blaze plan so far – Miguel Espeso Aug 05 '22 at 14:46