Verification email with token in passport.js

Makromat picture Makromat · Mar 4, 2015 · Viewed 14.6k times · Source

I just looking for solution which makes verification email with token for my local autentification in passport.js Is there some plugin or component for node which can make me verification easyer? Or I have to do it myself?

My controller

exports.postSignup = function(req, res, next) {
  req.assert('email', 'Email is not valid').isEmail();
  req.assert('password', 'Password must be at least 4 characters long').len(4);
  req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);

  var errors = req.validationErrors();

  if (errors) {
    req.flash('errors', errors);
    return res.redirect('/signup');
  }

  var user = User.build({
    email: req.body.email,
    password: req.body.password,
  });

  User
  .find({ where: { email: req.body.email } })
  .then(function(existingUser){
    if (existingUser) {
      req.flash('errors', { msg: 'Account with that email address already exists.' });
      return res.redirect('/signup');
    }

    user
    .save()
    .complete(function(err){
      if (err) return next(err);
      req.logIn(user, function(err){
        if (err) return next(err);
        res.redirect('/');
      });
    });
  }).catch(function(err){
    return next(err);
  });
};

Thanks for any opinion!

Answer

Tom picture Tom · Sep 28, 2016

Implementing this yourself is pretty straightforward.

The pseudocode:

//A user registers
    //User is stored along with a random token string and a variable set to false
    //User is sent a verification email

//Verification email has a link with the random token and a unique ID for that user

//Link goes to a route that takes the token as a parameter


//Match the user and the random token

//If they match - change a variable to verified

The package I use to generage the random string is: https://www.npmjs.com/package/randomstring

Local signup strategy

passport.use('local-signup', new LocalStrategy({
        // by default, local strategy uses username and password, we will override with email
        usernameField: 'email',
        passwordField: 'password',
        passReqToCallback: true // allows us to pass back the entire request to the callback
    },

    function (req, email, password, done) {
        // asynchronous
        // User.findOne wont fire unless data is sent back
        process.nextTick(function () {
            // find a user whose email is the same as the forms email
            // we are checking to see if the user trying to login already exists
            User.findOne({'local.email': email}, function (err, user) {
                // if there are any errors, return the error
                if (err) {
                    return done(err);
                }

                // check to see if theres already a user with that email
                if (user) {
                    console.log('that email exists');
                    return done(null, false, req.flash('signupMessage', email + ' is already in use. '));

                } else {
                    User.findOne({'local.username': req.body.username}, function (err, user) {
                        if (user) {
                            console.log('That username exists');
                            return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
                        }

                        if (req.body.password != req.body.confirm_password) {
                            console.log('Passwords do not match');
                            return done(null, false, req.flash('signupMessage', 'Your passwords do not match'));
                        }

                        else {
                            // create the user
                            var newUser = new User();

                            var permalink = req.body.username.toLowerCase().replace(' ', '').replace(/[^\w\s]/gi, '').trim();

                            var verification_token = randomstring.generate({
                                length: 64
                            });


                            newUser.local.email = email;

                            newUser.local.password = newUser.generateHash(password);

                            newUser.local.permalink = permalink;

                            //Verified will get turned to true when they verify email address
                            newUser.local.verified = false;
                            newUser.local.verify_token = verification_token;

                            try {
                                newUser.save(function (err) {
                                    if (err) {

                                        throw err;
                                    } else {
                                        VerifyEmail.sendverification(email, verification_token, permalink);
                                        return done(null, newUser);
                                    }
                                });
                            } catch (err) {

                            }
                        }
                    });
                }
            });
        });
    }));

I use a combination of /permalink/random-token for the verification URL

The route should look like this:

app.get('/verify/:permaink/:token', function (req, res) {
        var permalink = req.params.permaink;
        var token = req.params.token;

        User.findOne({'local.permalink': permalink}, function (err, user) {
            if (user.local.verify_token == token) {
                console.log('that token is correct! Verify the user');

                User.findOneAndUpdate({'local.permalink': permalink}, {'local.verified': true}, function (err, resp) {
                    console.log('The user has been verified!');
                });

                res.redirect('/login');
            } else {
                console.log('The token is wrong! Reject the user. token should be: ' + user.local.verify_token);
            }
        });
    });