Passport deserialize function is removing user from the session - node.js

To be frank, I just started learning passport today. I feel I understand some of how passport is working, but I'm still trying to familiarize myself. My problem here (I think) is that my user is getting removed from the session, which is preventing me from reaching my authenticated routes. I console.logged the user id in the deserialize function to check if it was getting stored in the session, and it is ...
//serialize user into the session
passport.serializeUser(function(user,done){
done(null,user.id);
});
//deserialize the user from the session
passport.deserializeUser(function(id,done){
console.log('user id is: ' + id); //works and logs user id
User.findById(id, function(err,user){
done(err,user);
});
});
Here are my routes and passport middleware ...
app.post('/login', function(req,res,next){
passport.authenticate('local-login', function(err,user,info){
if(err){
console.log("we have an internal error!");
return next(err);
}
if(!user){
return res.send({success:false, message:'failed to login!'});
}else{
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
}
})(req,res,next);
});
//route middleware to make sure that a user is logged in
function isLoggedIn(req,res,next){
//if the user is authenticated in the session, carry on
if(req.isAuthenticated()){
next();
}
//if they are not authenticated in the session, redirect them to the home page
res.redirect('/');
}
Any help, insights, advice is greatly appreciated; thanks!

It's because you're always redirecting the user to the index page in your isLoggedIn middleware. Need to use return:
function isLoggedIn(req,res,next){
if(req.isAuthenticated()){
next();
// STOPS further execution of this function and prevents redirecting the user
return;
}
res.redirect('/');
}
Keep in mind that it's just JavaScript - no framework does any changes - Express, Passport, even Promises are pure JS and they don't modify the way the virtual machine works. GL!
p.s.
If things go wrong, especially in the beginning, I recommend using if-else statement. You wouldn't have problems this way:
if (req.isAuthenticated()) {
next();
} else {
res.redirect('/');
}

Related

Express session getting cleared between routes

I have below two routes in which I have used express-session to manage user session.
app.post('/login',function(req,res){
//all variables defined here
if(usernm&&passwd){
connection.query("select * from session where username=?;",[usernm],function(err,result){
if(err) throw err;
else if(result.length!=0){
res.send("Already signed in with username "+usernm);
}
else{
connection.query("select * from signup where username=? and passwd=?;",credentials, function(err1, result){
if(err1) throw err1;
if(result.length!=0)
{
req.session.loggedin=true;
req.session.username=usernm;
var sql1="insert into session values('"+usernm+"','"+req.sessionID+"');";
connection.query(sql1,function(err, result){
if(err) throw err;
else{
console.log(req.sessionID);
res.send('Logged in successfully with username '+req.session.username);
}
});
}
else{
obj={'status':'error','message':'Incorrect Username and/or Password'};
obj=JSON.stringify(obj);
res.send(obj);
}
});
}
});
}
});
app.get('/logout',(req,res)=>{
var sessionId=req.sessionID;
var username=req.session.username;
console.log(sessionId,username);
req.session.loggedin=false;
req.session.username=null;
req.session.destroy();
connection.query("Delete from session where sessionID=? or username=?;",[sessionId,username],function(err,result){
if(err) throw err;
else{
res.send("User signed out successfully!");
}
});
});
I am using curl to test my api endpoints. So, the problem is that when I send a post http request to login, I get a sessionID on console. But when I hit logout using curl, the sessionID printed here is different from the one printed in login route, which means the sessionID gets changed in between the routes.
Also, the console.log in logout prints req.session.username as undefined, which it shouldn't do.
As far as I know, this problem relates to session data getting cleared between two page requests.
I wonder why it is happening. Please help me to find the reason of above behaviour so that I can go ahead with my application.
Thank You!

Can I use res.redirect and res.send simultaneously in Node.js?

I want to print user's info on the landing page after they get logged in. I am using res.redirect to redirect to landing page i.e index1.html after a user get successfully logged in.
Now I want to get user info. Here is my node.js code:
app.post('/login-feedback', function (req, res) {
dbConn.then(function(db) {
delete req.body._id; // for safety reasons
var dd = db.db("customermanagement");
dd.collection('admin').findOne({username: req.body.username, password: req.body.password}, function(err, user){
if(err) {
console.log(err);
}
else if(user){
req.session.user = user;
console.log( req.session.user.username);
res.status(200);
res.redirect('/index1.html');
}
else {
res.redirect('/login1.html');
}
});
});
});
I would love if you will tell me explicitly everything step by step.
If you want to send data to a static file, like an html file, you should use res.render :
res.render('/login1.html', {user: req.session.user.username})

Passportjs local strategy returns 'invalid credentials', is there any way to override this?

passport has been a real thorn in my side lately. I'm trying to register a user and have a custom callback. However, if the user doesn't supply a username and password and just submits the form I get back an 'invalid credentials' error. I would like to intercept this before then so I can format it like the rest of my error messages, and send it back.
Is there any way to do this? I dug through passport and couldn't find anything.
You can make that happen on the callback, look at this example (not pretty code but it illustrates the idea well). In this case, if the user credentials are valid but the user has not confirmed their email address, I am returning a flash object to notify the user of the error. You could also make user of the /success or /failure options on passport to call specific url's upon a success or failure of authentication.
app.post('/login', function(req, res, next){
passport.authenticate('local', function(error, user, info){
if(error){
//handle error here
}
else{
if(!user){
res.status(404).end();
} else{
req.logIn(user, function(error){
if(error) return res.status(500).end();
if(!user.isEmailConfirmed){
req.session.flash = {
type: 'warning',
intro: 'Achtung',
message: 'error message'
};
res.redirect('/');
}
else res.redirect('/');
});
}
}
})(req, res, next);
});

How to perform login redirect with NodeJS/ExpressJS

I am in search of a way to redirect a user to the previous page he or she was on after successful login. For instance, if a user wants to use a feature only accessible to members, he or she would be redirected to a login page, and on a successful login, would then be redirected to what ever page the user was trying to access before being logged in. Here is what I have so far:
Middleware:
function requireLogin(req, res, next){
if (!req.user) res.redirect('/login');
else next();
}
Login:
app.post('/login', function(req,res){
User.findOne({email: req.body.email}, function(err, user){
if (!user) res.render('login.jade',{error: 'Invalid email or password.'});
else{
if (bcrypt.compareSync(req.body.password,user.password)) {
req.session.user = user; //set cookie session
res.redirect('/forums');
}
else res.render('login.jade',{error: 'Invalid email or password.'});
}
});
});
Example route:
app.post('/article/comment', requireLogin, function(req,res){...
I have seen a few SO posts about this subject, but no really qualifying answers(especially without PassportJS). Suggestions?
I was able to solve this issue with a few simple additions to my existing code:
function requireLogin(req, res, next){
if (!req.user) {
req.session.prevUrl = req.body.url;
if(req.xhr) res.send({"err":"usrErr"});
else res.redirect('/login');
}
else next();
}
app.post('/login', function(req,res){
User.findOne({email: req.body.email}, function(err, user){
if (!user) res.render('login.jade',{error: 'Invalid email or password.'});
else{
if (bcrypt.compareSync(req.body.password,user.password)) {
req.session.user = user; //set cookie session
if(req.session.prevUrl) res.redirect(req.session.prevUrl);
else res.redirect('/forums');
}
else res.render('login.jade',{error: 'Invalid email or password.'});
}
});
});
I saved the page a user was on when trying to access a feature only available to members as session data. Note, this is a much better solution than the 'back' solution, as (for instance if the user inputs the wrong login information, the previous page will not now be the login page, but still the page saved in the session). With the session storage now saved, a simple check in the login can be done to see if the saved session url exists. If it does, redirect to the saved session url, else redirect to the default home.
P.S. The line, if(req.xhr) res.send({"err:"usrErr"}); is a check to see if the request made was a JQuery request. This is because a redirect will not be performed if the request was made through JQuery. The err response allowed me to do a simple check in my JQuery function to perform a redirect if the user did not exist. Note, the session data is still saved in this case so, simply add
window.location.href='/login'; to your function in order to perform the redirect within the script.
Thanks to #mikeyhew for pointing me in the right direction.
You can use res.redirect('back'), the only caveat is it relies upon the http-referer header which is not reliable, when the http-referer header is omitted it defaults to /. Check the docs.
It would help if you stored the request url from your requireLogin middleware for future use:
function requireLogin(req, res, next){
if (!req.user) {
var postLoginRedirectUrl = req.originalUrl;
// store this url somehow (session or query params?)
res.redirect('/login');
} else {
next();
}
}
I've seen some sites use query params; you just have to make sure the url doesn't get lost in the login process.
Then when the user logs in successfully, just redirect them to that url.

How can I report an invalid login properly with Express and PassportJS?

I've successfully implemented passport-local into my Express/Mongoose web-app but I'm having trouble figuring out how to render a failed login message properly.
Here's my login route:
app.get('/login', function(req, res) {
res.render('user/login', {
});
});
With a route like that how am I supposed to report an invalid login? If the login is successful it will write the id/username to the req.user object but that doesn't help me in the "GET /login" route because if it's successful you will get redirected to the page you want to go.
That means req.user will always be undefined when you GET the login page.
I want to be able to write out a message saying something like 'yo, invalid login!' when the following things happen:
The user does not exist.
The password supplied does not match but the user existed.
I might want to output a different message depending on what occurred.
When I implemented the LocalStrategy I used this code:
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, fn) {
User.findOne({'login.email': email}, function(err, user) {
// Error was thrown.
if (err) {
return fn(err);
}
// User does not exist.
if (!user) {
return fn(null, false);
}
// Passwords do not match.
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false);
}
// Everything is good.
return fn(null, user);
});
}
));
As you can see there are some problems but this is how the author of PassportJS set up his application. How are we supposed to access what the Strategy returns?
Like if it throws an error, what am I supposed to even call to get access to err?
Thanks.
In the latest version of Passport, I've added support for flash messages, which make it easy to do what you are asking.
You can now supply a third argument to done, which can include a failure message. For example:
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false, { message: 'yo, invalid login!' });
}
Then, set failureFlash to true as an option to authenticate().
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login',
failureFlash: true });
In this case, if authentication fails, the message will be set in the flash, ready for you to render it when you display the login page again.
Custom callbacks are also perfectly fine. The built in options just make it simpler to accomplish common tasks.
Also, I'm curious: you mention that there are problems with the sample. What do you think should be improved? I want to make the examples as good as possible. Thanks!
(For more details, see this comment on issue #12).
You can use the custom callback or middleware functionality to have more control. See the Authentication section of the guide for examples.
For example, a custom callback might look like:
app.get('/login', function(req,res,next) {
passport.authenticate('local', function(err,user) {
if(!user) res.send('Sorry, you\'re not logged in correctly.');
if(user) res.send('Skeet skeet!');
})(req,res,next);
});
Alternatively, you could always redirect both responses:
app.get('/login',
passport.authenticate('local', { successRedirect: '/winner',
failureRedirect:'/loser' }));
Or redirect the failure with simple middleware:
app.get('/login', ensureAuthenticated,
function(req,res) {
// successful auth
// do something for-the-win
}
);
// reusable middleware
function ensureAuthenticated(req,res,next) {
if(req.isAuthenticated()) {return next();}
res.redirect('/login/again'); // if failed...
}

Resources