Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending back a JSON response when failing Passport.js authentication

I'm using Node.js as a backend API server for an iPhone client. I'm using Passport.js to authenticate with a local strategy. The relevant code is below:

// This is in user.js, my user model UserSchema.static('authenticate', function(username, password, callback) {     this.findOne({ username: username }, function(err, user) {         if (err){             console.log('findOne error occurred');             return callback(err);         }         if (!user){             return callback(null, false);         }         user.verifyPassword(password, function(err, passwordCorrect){             if (err){                 console.log('verifyPassword error occurred');                 return callback(err);             }             if (!passwordCorrect){                 console.log('Wrong password');                 return callback(err, false);             }             console.log('User Found, returning user');             return callback(null, user);         });     }); }); 

and

// This is in app.js app.get('/loginfail', function(req, res){     res.json(403, {message: 'Invalid username/password'}); });  app.post('/login',     passport.authenticate('local', { failureRedirect: '/loginfail', failureFlash: false }),     function(req, res) {        res.redirect('/'); }); 

Right now, I have managed to redirect a failed login to /loginfail, where I send back some JSON to the iPhone client. However, this doesn't have enough granularity. I want to be able to send back the appropriate errors to the iPhone client, such as: "No user found" or "Password is wrong". With my existing code, I don't see how this can be accomplished.

I tried to follow the examples for a custom callback on the passport.js site, but I just can't get it to work due to lack of node understanding. How could I modify my code so that I'd be able to send back a res.json with an appropriate error code/message?

I am trying something like this now:

// In app.js app.post('/login', function(req, res, next) {     passport.authenticate('local', function(err, user, info) {         if (err) { return next(err) }         if (!user) {             console.log(info);             // *** Display message without using flash option             // re-render the login form with a message             return res.redirect('/login');         }         console.log('got user');         return res.json(200, {user_id: user._id});     })(req, res, next); });  // In user.js UserSchema.static('authenticate', function(username, password, callback) {     this.findOne({ username: username }, function(err, user) {         if (err){             console.log('findOne error occurred');             return callback(err);         }         if (!user){             return callback(null, false);         }         user.verifyPassword(password, function(err, passwordCorrect){             if (err){                 return callback(err);             }             if (!passwordCorrect){                 return callback(err, false, {message: 'bad password'});             }             console.log('User Found, returning user');             return callback(null, user);         });     }); }); 

But back when I try to console.log(info), it just says undefined. I don't know how to get this custom callback working...Any help would be appreciated!

like image 800
kurisukun Avatar asked Mar 13 '13 14:03

kurisukun


1 Answers

I had a similar issue with Passport and failed login responses. I was building an API, and wanted all responses to be returned as JSON. Passport responds to an invalid password with status: 401 and body: Unauthorized. That's just a text string in the body, not JSON, so it broke my client which expected all JSON.

As it turns out, there is a way to make Passport just return the error to the framework instead of trying to send a response itself.

The answer is to set failWithError in the options passed to authenticate: https://github.com/jaredhanson/passport/issues/126#issuecomment-32333163

From jaredhanson's comment in the issue:

app.post('/login',   passport.authenticate('local', { failWithError: true }),   function(req, res, next) {     // handle success     if (req.xhr) { return res.json({ id: req.user.id }); }     return res.redirect('/');   },   function(err, req, res, next) {     // handle error     if (req.xhr) { return res.json(err); }     return res.redirect('/login');   } ); 

This will invoke the error handler after Passport calls next(err). For my app, I wrote a generic error handler specific to my use case of just providing a JSON error:

// Middleware error handler for json response function handleError(err,req,res,next){     var output = {         error: {             name: err.name,             message: err.message,             text: err.toString()         }     };     var statusCode = err.status || 500;     res.status(statusCode).json(output); } 

Then I used it for all api routes:

var api = express.Router(); ... //set up some routes here, attached to api ... // error handling middleware last api.use( [         handleError         ] ); 

I didn't find the failWithError option in the documentation. I stumbled upon it while tracing through the code in the debugger.

Also, before I figured this out, I tried the "custom callback" mentioned in the @Kevin_Dente answer, but it didn't work for me. I'm not sure if that was for an older version of Passport or if I was just doing it wrong.

like image 166
Mnebuerquo Avatar answered Oct 03 '22 02:10

Mnebuerquo