How to show custom error messages using passport and express

I check if the email already exist when registering a user. If a user already exist I pass the error message “email already exists”. But in the front end it shows “Unauthorized” error 401. I want to pass the error message which I pass to the front end from the back end but it pass the default message. below is how I check if a user already exist and send the error message,

exports.register = new LocalStrategy({
    usernameField: 'email',
    passReqToCallback: true
}, function(req, email, password, done,res) {
    var newUser = new User({
        email: email,
        password: password,
        name: req.body.fname
    });
    var searchUser = {
        email: email
    };
    User.findOne(searchUser, function(err, user) {
        if (err) return done(err);
        console.log("err "+err);

        if (user) {
            return done(null, false, {
                message: "email already exists"
            });
        }
        newUser.save(function(err) {
        console.log("newUser "+newUser);
            done(null, newUser);
        })
    });
});

I use passport for authentication,

authRouter.post('/signup', passport.authenticate('local-register'),function(req, res) {
    createSendToken(req.user, res);
});

The error message is not the one I pass to the front end. It shows unauthorized error, which is the default error. When I print the error message in the console in front end it shows,

Object {data: "Unauthorized", status: 401, config: Object, statusText: "Unauthorized"}

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

You are not saying what output you want in your front end, but I’m guessing you want to have data to be the message that you set in your LocalStrategy.

Here is one way to do that:

authRouter.post('/signup', function(req, res, next) {
  passport.authenticate('local-register', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { 
        res.status(401);
        res.end(info.message);
        return;
    }
    createSendToken(req.user, res);
  })(req, res, next);
});

Solution 2

You can take advantage of passReqToCallback: true in passport.

With this option enabled, req will be passed as the first argument to the verify callback.

Also by taking advantage of flash messages.

Here is basic example of how you use it,

// your strategy
passport.use(new LocalStrategy({
    passReqToCallback: true
  },
    (req, username, password, done) => {
      User.findOne({ username: username }, (err, user) => {
        if (err) done(err)
        if (!user) {
          console.log('user does not exists')
          return done(null, false, req.flash('message', 'User does not exist' ))
        } else {
          console.log('user exists')
          return done(null, user, req.flash('message', 'User exist'))
        }
      })
    }
  ))

// your GET login
router.get("/login", (req, res) => {
  var message = req.flash('message')
  res.render("pages/login", { message })
})

Solution 3

As mentioned in other answers you can manually check the info parameter and then return your own message based on that:

// ❌ MESSY ❌
authRouter.post('/signup', function(req, res, next) {
    passport.authenticate('local-register', function(err, user, info) {
        if(info.name === 'UserExistsError' ) {
            return done(null, false, {
                message: "Email already exists"
            });
        } else if (info.name === 'IncorrectUsernameError') {
          return done(null, false, {
              message: "Email does not exist"
          });
        } else if(....

But A MUCH cleaner way is to just specify custom error messages when you create the Account Mongoose model:

var Account = new Schema({
    ...
});

var options = {
    errorMessages: {
        UserExistsError: 'Email already exists',
        IncorrectUsernameError: 'Email does not exist',
        IncorrectPasswordError: ...
    }
};

Account.plugin(passportLocalMongoose, options);

Then in your signup route you can simply return the info.message to the user.

// ✨ CLEAN ✨
authRouter.post('/signup', function(req, res, next) {
    passport.authenticate('local-register', function(err, user, info) {
        return done(null, false, {
            message: info.message
        });
    });
});

Solution 4

If you are using passReqToCallback = true then you can send it just like

if (!isMatch) { return done({ message: 'Incorrect password' }); }

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply