How to passing message to client in passportjs - node.js

Currently, I got the problem to get message from nodejs
In client (that separated from nodejs),
'use strict';
angular.module('app').controller('RegisterCtrl', function ($scope, alert, $auth) {
$scope.submit = function () {
$auth.signup({
email: $scope.email,
password: $scope.password
})
.then(function (res) {
alert('success', 'Account Created!', 'Welcome, ' + res.data.user.email + '! Please email activate your account in the next several days.');
})
.catch(function (err) {
alert('warning', 'Unable to create account :(', err.message);
});
};
});
Although, On Nodejs passport I get the error "email already taken" but it can't pass this message into client, client just receive 401 (unauthorized).
I also install connect-flash and set
app.post('/auth/register', passport.authenticate('local-register', {
failureFlash : true // allow flash messages
}), function (req, res) {
emailVerification.send(req.user.email, res, req);
createSendToken(req.user, res);
});
but still no luck. How can I get this message? Thanks

The flash message is set to req and not returned in HTTP response. If you were to render HTML on the server you could use it, but it does not work for AJAX.
Whilst I don't know why you are using authentication library for handling registrations, you could achieve this with the custom callback. An untested example:
app.post('/auth/register', function(req, res, next) {
passport.authenticate('local-register', function(err, user, info) {
if (err) return next(err);
if (user) {
req.logIn(user, function(err) {
if (err) return next(err);
emailVerification.send(req.user.email, res, req);
createSendToken(req.user, res);
});
// Register failed, flash message is in info
} else {
res.status(400).json(info);
}
})(req, res, next);
});

Related

Passportjs sessions fail when JSON response is set first

I'm using express + passport to handle authentication for a react app. Because I'm using react, I don't want to automatically redirect the login request via express, instead I want to return JSON.
However, I've noticed that simply returning JSON and then calling next(null, user) seems to prevent passport from running the serializeUser method. By calling next passport should automatically call the req.login function, which doesn't appear to happen when I've sent json.
It appears that if I manually call req.login and then send the JSON, it works (the serialize method is called, the user has a session).
Is there a way to send a json response and call next without having to manually invoke req.login? Feels like I'm just not doing things the correct way.
module.exports = function(app) {
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.json({ errorMessage: err.message });
return next(err);
}
if (!user) {
res.json({ errorMessage: 'Invalid user.' });
return next(err);
}
// This fails to invoke the session serialization:
// res.json({
// success: true,
// username: user.username
// });
// return next(null, user);
// This works:
req.logIn(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
res.json({
success: true,
username: user.username
});
return;
});
})(req, res, next);
});
};

Passport send callbacks for errors to be able to res.json

How can i catch errorfrom local-signup? i cant find a way to catch them.
The idea is to catch the errors, then send them as a message back to client with
res.json
Current output:
already taken????????????????????
node.js
router.post('/register', passport.authenticate('local-signup'),function(req, res, next) {
console.log("registration");
console.log("ERROR?");
console.log(req);
console.log(res);
// res.json({type: 'danger',message: info});
});
passport:
passport.use('local-signup', new LocalStrategy({
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true
},
function(req, username, password, done) {
process.nextTick(function() {
console.log("doing local signup");
Account.findOne({username : username }, function(err, user) {
if (err)
return done(err);
if (user) {
console.log("already taken????????????????????");
return done(null, false, { message: 'That username is already taken.'});
return done(err);
} else {
var newUser = new Account();
newUser.username = username;
newUser.password = newUser.encryptPassword(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
Update:
I tried custom error callback code, but then i cant get req property to send request back to client.
I also tried to move the authenticate middleware to be called within the function req, res, next, but then it wont be called at all.
Do you have a generic error handler in your app? If you do, you could pass the option failWithError to the LocalStrategy.
Example:
passport.authenticate('local-signup', { failWithError: true })
passport code snipped:
...
if (options.failWithError) {
return next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus));
}
...
And in case, when error occurs, passport will pass the error for the error handler.
Error handler example:
...
// it has to be the last operation in the app object
app.use(errorHandler);
function errorHandler(err, req, res, next) {
if(typeof error == AuthenticationError) {
res.status(403);
res.json('error', { error: err });
}
else{ /* anything else */}
}
Hope it help you.

How to handle passport authentication response and show it to the user

I'm authenticating my nodeJs app using passport local strategy. Everything is working fine. But how can I show the user appropriate message that he has entered invalid login credentials. My present code is just sending 401 unauthorized error on screen.
Here is my code
passport.use(new LocalStrategy(function(username, password, callback) {
User.findOne({
username : username
}, function(err, user) {
if (err) {
return callback(err);
}
// No user found with that username
if (!user) {
return callback(null, false);
}
// Make sure the password is correct
user.verifyPassword(password, function(err, isMatch) {
if (err) {
return callback(err);
}
// Password did not match
if (!isMatch) {
return callback(null, false);
}
// Success
return callback(null, user);
});
});
}));
exports.isLocalAuthenticated = passport.authenticate('local', {
session : true
});
router.post('/', authController.isLocalAuthenticated, function(req, res) {
//here I want to show the error message to user
});
The documentation has clearly described your case under Custom Callback section.
You need to add custom callback like this:
exports.isLocalAuthenticated = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); } //error exception
// user will be set to false, if not authenticated
if (!user) {
res.status(401).json(info); //info contains the error message
} else {
// if user authenticated maintain the session
req.logIn(user, function() {
// do whatever here on successful login
})
}
})(req, res, next);
}
You don't need to specify the latter callback.

Express redirect after login authentication

I'm working on an app which has node.js and express on the server, mongodb for the db and Backbone.js on the front-end. I'm working on enabling user logins, so I've used the passport.js library. I have a problem with my login 'post' method: It is not redirecting to another page (well it's an SPA, so I mean rendering a Backbone view). Here's the code:
//standard express setup...
app.post('/api/auth', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
req.session.messages = [info.message];
return res.redirect('/')
}
req.logIn(user, function(err) {
if (err) {
return next(err);
} else {
console.log('yup, working'); //Can see the response in the console
return res.redirect('/api');
}
});
})(req, res, next);
});
app.get('/api', function (request, response) {
response.send( 'Login successful!' );
});
So I can see the console.log message fine, and a GET request for the route IS triggered...but nothing actually happens. So I'm thinking that I've misunderstood how 'res.redirect' works - I want to navigate to that route upon the login success. I've thought about using window.location, but is this a good long-term solution? I'm not using any html templates on the server, so I can't (I don't think) do something as simple as 'res.render('index')'
What would be the best way to approach this? Thanks in advance.
I had a face-palm moment a few days after asking this question where I realized I had failed to grasp a core concept correctly. Let me answer my own question:
Rather than trying to serve the page from the server, I just need to send a response to the client and have Backbone do the rendering, depending on the response received. So my server does something more like this:
controller.post('/user', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
user.save(function(err) {
if(err){
console.log(err);
return res.json(401);
} else {
return res.json(200); //SEND RESPONSE - do not try to redirect
}
});
Then I do the page loading in the Backbone View, like this:
login: function (e) {
e.preventDefault();
console.log('submitting login request');
var formValues = {
username: $('#inputEmail').val(),
password: $('#inputPassword').val()
};
var user = new User(); //user model
user.set(formValues);
function saveUserfunction (){
if(user) {
user.save(this.formValues, {
success: function(model, response, options) {
if (response == 200) {
console.log('success :' + response);
window.location.href = 'http://localhost:4711/#/api/menu_auth'; //Here's my redirect - the router is listening for this route and will render accordingly
} else {
console.log('error: '+response);
}
}, error: //Error handling etc.
As Pheonix pointed out in the comments, the best way to do this would be to listen to the the user model.

Express Passport (node.js) error handling

I've looked at how error handling should work in node via this question Error handling principles for Node.js + Express.js applications?, but I'm not sure what passport's doing when it fails authentication. I have the following LocalStrategy:
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, next) {
User.find({email: UemOrUnm}, function(err, user){
if (err) { console.log('Error > some err'); return next(err); }
if (!user) { console.log('Error > no user'); return next('Incorrect login or password'); }
if (password != user.password) {
return next(Incorrect login or password);
}
return next(null, user);
});
}
));
After I see 'Error > some err' console printout, nothing else happens. I would think it should continue on the the next path with an error parameter, but it doesn't seem to do that. What's going on?
The strategy-implementation works in conjunction with passport.authenticate to both authenticate a request, and handle success/failure.
Say you're using this route (which is passed an e-mail address and a password):
app.post('/login', passport.authenticate('local', {
successRedirect: '/loggedin',
failureRedirect: '/login', // see text
failureFlash: true // optional, see text as well
});
This will call the code in the strategy, where one of three conditions can happen:
An internal error occurred trying to fetch the users' information (say the database connection is gone); this error would be passed on: next(err); this will be handled by Express and generate an HTTP 500 response;
The provided credentials are invalid (there is no user with the supplied e-mail address, or the password is a mismatch); in that case, you don't generate an error, but you pass a false as the user object: next(null, false); this will trigger the failureRedirect (if you don't define one, a HTTP 401 Unauthorized response will be generated);
Everything checks out, you have a valid user object, so you pass it along: next(null, user); this will trigger the successRedirect;
In case of an invalid authentication (but not an internal error), you can pass an extra message along with the callback:
next(null, false, { message : 'invalid e-mail address or password' });
If you have used failureFlash and installed the connect-flash middleware, the supplied message is stored in the session and can be accessed easily to, for example, be used in a template.
EDIT: it's also possible to completely handle the result of the authentication process yourself (instead of Passport sending a redirect or 401):
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (! user) {
return res.send({ success : false, message : 'authentication failed' });
}
// ***********************************************************************
// "Note that when using a custom callback, it becomes the application's
// responsibility to establish a session (by calling req.login()) and send
// a response."
// Source: http://passportjs.org/docs
// ***********************************************************************
req.login(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
});
What Christian was saying was you need to add the function
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({success:true});
});
So the whole route would be:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (! user) {
return res.send(401,{ success : false, message : 'authentication failed' });
}
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
});
source: http://passportjs.org/guide/login/
You need to add req.logIn(function (err) { }); and do the success redirect inside the callback function
Some time has passed and now the most right code will be:
passport.authenticate('local', (err, user, info) => {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (!user) {
return res.status(401).send({ error: 'Authentication failed' });
}
req.login(user, (err) => {
if (err) {
return next(err);
}
return res.status(202).send({ error: 'Authentication succeeded' });
});
});
I found this thread very useful!
https://github.com/jaredhanson/passport-local/issues/2
You could use this to return error and render it in form.
app.post('/login',
passport.authenticate('local', { successRedirect: '/home', failWithError: true }),
function(err, req, res, next) {
// handle error
return res.render('login-form');
}
);
This is what I got after console.log(req) at the failler route.
const localStrategy = new LocalStrategy({ usernameField: "email" }, verifyUser);
passport.use(localStrategy);
const authenticateWithCredentials = passport.authenticate("local", {
failureRedirect: "/api/auth/login-fail",
failureMessage: true,
});
validation method find your user from db and throw error to the cb if there is any
const verifyUser = async (email, password, cb) => {
const user = await User.findOne({ email });
if (!user) return cb(null, false, { message: "email/password incorrect!" });
const isMatched = await user.comparePassword(password);
if (!isMatched)
return cb(null, false, { message: "email/password incorrect!" });
cb(null, {
id: user._id,
email,
name: user.name,
});
};
now setup your route
router.post("/sign-in", authenticateWithCredentials,(req, res) => {
res.json({user: req.user})
});
router.get("/login-fail", (req, res) => {
let message = "Invalid login request!";
// if you are using typescript cast the sessionStore to any
const sessions = req.sessionStore.sessions || {};
for (let key in sessions) {
const messages = JSON.parse(sessions[key])?.messages;
if (messages.length) {
message = messages[0];
break;
}
}
res.status(401).json({ error: message });
});

Resources