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
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);
});