jaredhanson/passport-local

How to overwrite the "Bad Request" error message

hagemann opened this issue ยท 4 comments

Is there a way to overwrite the "Bad Request" error message if some of the required fields were not provided by the user? I would like to put something like "Required fields are missing". My current workaround is to handle such validation at the frontend.

This is related to #146

There is no way to overwrite the "Bad Request" message. After poking through the code, I've discovered that it's picked depending on the response status code.

The passport-local strategy is supposed to fail with status code 400 when the username or password are missing:

Strategy.prototype.authenticate = function(req, options) {
  options = options || {};
  // Why would one check for username and password in the query params!?
  var username = lookup(req.body, this._usernameField) || lookup(req.query, this._usernameField);
  var password = lookup(req.body, this._passwordField) || lookup(req.query, this._passwordField);
  
  if (!username || !password) {
    return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, 400);
  }
[...]

Passport picks the message using the status code:

// passport/lib/middleware/authenticate.js - line ~171
if (options.failWithError) {
  return next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus));
}
res.end(http.STATUS_CODES[res.statusCode]);

// 400 - Bad Request
lerit commented

you can do this:
passport.authenticate('local', { failWithError: true })
the fail info will pass to next ,and you can catch it.

If you are using Express and sessions, you could use connect-flash. I've used it successfully in the past by declaring the custom message when initializing the passport strategy. Here's an example using passport-local, Express, and PostgreSQL:

Initialize passport-local:

const options = {};

passport.use( 
  'local',
  new LocalStrategy(options, (username, password, done) => {
  
    // Finds user from database
    db.any('SELECT * FROM "user" WHERE username=$1', [username])
      .then((user) => {
      
        // Checks to see if user exists
        if (!user[0]) {
          return done(null, false, {
            // CUSTOM MESSAGE
            message: 'Incorrect username and password combination.'
          });
        }
        
        // Checks to see if passwords match
        if (!bcrypt.compareSync(password, user[0].password)) {
          return done(null, false, {
            // CUSTOM MESSAGE
            message: 'Incorrect username and password combination.'
          });
        }
        
        // If user exists and entered correct password, return user
        return done(null, user[0]);
      })
      .catch((err) => {
        // Triggers when there is an error with the database or query
        return done(null, false, {
          // CUSTOM MESSAGE
          message: 'There was an error!'
        });
      });
  })
);

In the route where you call passport.authenticate:

// Login Route
app.post(
  '/auth/login',
  passport.authenticate('local', {
    failureRedirect: '/',
    // Set failureFlash to true
    failureFlash: true
  }),
  (req, res) => {
    return res.redirect('/home');
  }
);

Now you'll be able to access your custom message through req.flash.

If you would like to check out the repo where I used this, you can check it out here. Anything marked with 'auth' or 'authentication' is where you'll find everything.

A custom callback can be provided to allow the application to handle success or failure.

router.post('/login', (req, res, next) => {
    passport.authenticate('local', (err, user, info) => {
        if (err) return next(err); 
        
        if (!user) {
           return res.status(400).json({message: 'Required fields are missing'});
            // or return next(new Error(info.message));
        }

        // log in the user
        return res.json({
            user: req.user,
            access_token: auth.getToken(req.user), // jwt token in my case you can also use req.logIn(user, cb)
            message: 'Logged in Successful',
        });

    })(req, res, next);
});