1

I'm setting up an email verification system when the user signs up, the email is sent correctly but I don't know how to check if the user has clicked on the link or not.

I've tried to make a variable "verified" and when the user is verified it will change but it won't work.

I've also tried to check if the user is verified when the token changes but it won't work even when I change it manually.

exports.signup = (req, res) => {
  const newUser = {
    email: req.body.email,
    password: req.body.password,
    confirmPassword: req.body.confirmPassword,
    handle: req.body.handle
  };

  const { valid, errors } = validateSignupData(newUser);

  if (!valid) return res.status(400).json(errors);

  const noImg = 'no-image.png';

  let token, userId;
  db.doc(`/users/${newUser.handle}`)
    .get()
    .then((doc) => {
      if (doc.exists) {
        return res.status(400).json({ handle: 'This username is already taken' });
      } else {
        return firebase
          .auth()
          .createUserWithEmailAndPassword(newUser.email, newUser.password);
      }
    })
    .then((data) => {
      userId = data.user.uid;
      return data.user.getIdToken();
    })
    .then((idToken) => {
      let currentUser = firebase.auth().currentUser
      token = idToken;
      let userCredentials = {
        handle: newUser.handle,
        email: newUser.email,
        createdAt: new Date().toISOString(),
        verified: currentUser.emailVerified,
        imageUrl: `https://firebasestorage.googleapis.com/v0/b/${
          config.storageBucket
          }/o/${noImg}?alt=media`,
        userId
      };
      currentUser.emailVerified = true
      currentUser.sendEmailVerification()
        .then(() => {
          console.log("Email sent successfully !");
        })
        .catch((err) => {
          console.error(err)
        })
      firebase.auth().onIdTokenChanged((user) => {
        if (currentUser.emailVerified) {
          userCredentials.verified = true
        }
      })
      return db.doc(`/users/${newUser.handle}`).set(userCredentials);
    })
    .then(() => {
      return res.status(201).json({ token });
    })
    .catch((err) => {
      console.error(err);
      if (err.code === 'auth/email-already-in-use') {
        return res.status(400).json({ email: 'Email is already is use' });
      } else {
        return res
          .status(500)
          .json({ general: 'Something went wrong, please try again' });
      }
    });
};
No Name
  • 13
  • 3
  • 1
    firebase supports this out of the box: https://firebase.googleblog.com/2017/02/email-verification-in-firebase-auth.html – imjared Sep 16 '19 at 22:11

2 Answers2

2

When you call user.sendEmailVerification(), Firebase sends an email to the use's email address with an unguessable link that verifies their email address. When the user clicks the link, they're taken to an auto-generated web page, which marks their email address as verified.

The actual verification of the email address happens outside of your app, possible even on a different machine. While the Firebase server status for the user is auto-updated when they click the link in the verification email, the app has no knowledge that this event occurred. It will learn about this fact when it refreshes the ID token of the user.

You have three main options:

  1. Wait for the ID token to refresh, which automatically happens every hour.
  2. Ask the user to click a link/button in the web app after they've verified their email address, and then call user.getIdToken(true) to force a refresh of their token.
  3. Automatically periodically refresh the user's ID token, for example once the focus comes back to your web page.

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thank you for your answer, I tried with firebase.auth().onIdTokenChanged() and then changing the value of the variable but it didn't work. – No Name Sep 17 '19 at 19:09
  • 1
    @Frank, how about a fourth option?.. Assuming you have a Firestore rule that checks `request.auth.token.email_verified == true`, then if the user attempts to access data and receives a *permission denied* error, refresh the token, then reload the page with the added URL parameter `refreshToken=false` to prevent an infinite refresh loop. I find this technique is very user friendly, although not super code friendly. – Ben Feb 24 '23 at 20:01
1

The link they click should change the variable, right? So, just add something like -

if (verified === false) {
  verified = true;
}
window.location.href = (homepage);

So if they click the link again, then it will just redirect them back to the homepage. If they clicked it for the first time, then it will "verify" them and redirected.

Rojo
  • 2,749
  • 1
  • 13
  • 34