Refresh passport.js session data using req.login() - node.js

I'm using passport.js. The problem is when I change user information such as user name, It's not changed until user re-login. If user logout and login again, the information changed. but I want to change in the session without re-logging in.
My deserializeUser function like below, It looks different original passport's example because it is pickup data from session instead of querying database. I want to up loading speed so I didn't use database query on deserialize function.
passport.deserializeUser(function(userSession, done){
var member = { id : userSession[0], name : userSession[1] };
done(null, member);
});
So I think I have to use req.login() like below, but the session wouldn't changed.
Members.findOne({_id : someId }, function(err, member){
member[username] = req.body.username;
member.save(function (err){
if(err) return next();
console.log('The original session :'+req.sessionId);
req.login(req.user, {}, function(err){
if (err) return next();
console.log('The new session :'+req.sessionId);
res.send(200);
});
})
})
The problem is original session and new session is same, How can I force to refresh req.user object?
I red this document but didn't get solution.
https://github.com/jaredhanson/passport/issues/208

Related

Passport session not updating

I'm trying to update the passport session after user info change using:
user.save(function(err) {
if (err) return next(err)
// What's happening in passport's session? Check a specific field...
console.log("Before relogin: "+req.session.passport.user.changedField)
req.login(user, function(err) {
if (err) return next(err)
console.log("After relogin: "+req.session.passport.user.changedField)
res.send(200)
})
})
But even after re-login, with no errors my passport session remains the same. I have to manually logout and log back in for the session info to update. Can anyone explain what's happening?
Passport sets req.user using the function you have provided with passport.deserializeUser. You are using req.session.passport.user which is incorrect. There is no need to re-login after change of user information - deserializeUser is called on every request, and this is where you fetch the user information from your database. Then you access it via req.user.

Authenticate user with passport through LinkedIn login

I have built a login system in Passport and works quite well. Now, I want to integrate LinkedIn login in my system. I already have clientID, clientSecret etc. needed to login. This is the code that is called when the LinkedIn login button is pressed.
passport.use('linkedin', new OAuth2Strategy({
authorizationURL: 'https://www.linkedin.com/uas/oauth2/authorization',
tokenURL: 'https://www.linkedin.com/uas/oauth2/accessToken',
clientID: clientid,
clientSecret: clientsecret,
callbackURL: '/linkedinLogin/linkedinCallbackUrlLogin',
passReqToCallback: true
},
function(req,accessToken, refreshToken, profile, done) {
console.log('authenticated');
console.log(accessToken);
req.session.code = accessToken;
process.nextTick(function () {
done(null, {
code : req.code
});
});
}));
Both the console.log() calls in the callback function are successfully fired, this means I am successfully logged in through LinkedIn and I receive my access token. The part where I connect with LinkedIn is thus correct, what I am missing is the part where I actually log in the user. As you can see, the callbackURL points to /linkedinLogin/linkedinCallbackUrlLogin. This is what I do in that route:
app.get('/linkedinLogin/linkedinCallbackUrlLogin', passport.authenticate('linkedin', {
session: false,
successRedirect:'/linkedinLogin/success',
failureRedirect:'/linkedinLogin/fail'
}));
I just specify a successRedirect and a failureRedirect. Note that if I put session : true I receive as an error Failed to serialize user into session, so for now I keep it to false.
The successRedirect is successfully called. In that route I call a GET request to LinkedIn to access some data about the user. I want to store this data in my DB and remember the user that logged in. This is how I do it:
https.get(
{
host: 'api.linkedin.com' ,
path: '/v1/people/~?format=json' ,
port:443 ,
headers : {'Authorization': ' Bearer ' + req.session.code}
},
function(myres) {
myres.on("data", function(chunk) {
var linkedinJsonResult = JSON.parse(chunk);
User.findOne({linkedinLogin : linkedinJsonResult.id}, function(err, userSearchResult){
if(err) {
throw err;
}
//user found, login
if(userSearchResult){
console.log(userSearchResult);
}
else {
//create user
var newUser = new User(
{
url : linkedinJsonResult.siteStandardProfileRequest.url,
name : linkedinJsonResult.firstName + " " + linkedinJsonResult.lastName,
linkedinLogin : linkedinJsonResult.id,
regDate : new Date()
}
);
//save user
newUser.save(function(err, user){
if(err){
throw err;
}
//login
console.log(user);
});
}
});
});
}
);
Let me explain the code there. After getting the data of the user I check the field "id" that is received. If this id matches one of my users' linkedinLogin field stored into the DB, I consider it already registered (the user has been found in the DB), thus I have to log him/her in. Otherwise I just create a new user using the data received from the GET request.
My question is, in both the cases - the user is found in my DB, or the user has to be created - how can I set req.user to be my user whenever it interacts with my website? Is it sufficient to just do req.user = userSearchResult (if the user is found, inside the if statement) or req.user = user (if the user has been created, inside the newUser.save() callback), or should I call some passport functions that will set it for me?
All the other passport functions related to the registration and login of users without using LinkedIn login are working fine. I am just worried about making this LinkedIn login work with passport.
Thank you.
passport.js will automatically set the req.user object to the object you will pass as the second argument to the done function of the strategy callback.
This means that you should do something like this:
function(req,accessToken, refreshToken, profile, done) {
console.log('authenticated');
console.log(accessToken);
req.session.code = accessToken;
process.nextTick(function () {
// retrieve your user here
getOrCreateUser(profile, function(err, user){
if(err) return done(err);
done(null, user);
})
});
}));
I hope this helps.

Passport update session isn't persisting

I'm trying to update my user session with a new name, an easy task I thought.
I'm a logged in user and in hitting a 'update' route and I've defined my own middleware to update the session:
module.exports = function(req, res, next) {
console.log(req.user);
req.login(req.body.user, function(err) {
if (err) return next(new Error('Error updating user profile'));
console.log('USER UPDATED *******', req.user);
next();
});
};
It took a bit of time to dig out the above code which should simply update the Passport session object. It correctly logs the previous session, and then the updated session but when I navigate to a new page after the inital response the user object is entirely lost and just returns {}.
Any ideas?
source
To log in a user and persist it into session passport uses a serialize function which typically stores user.id as a cookie and a deserialize function which retrieves that cookie and does a User.findById database call to find the associated user, and if found one, that's the user object that gets stored in req.user.
req.login passes whatever you pass it as the first argument directly to passport.serialize, which otherwise would've typically come from a strategy, which itself would've retrieved the user object from a database call, or created one.
So when you use req.login you need to pass it the user object that passport.serialize actually would've received, so that it could store the id in a cookie.
In your case, you were doing req.login(req.body.user, ... and since req.body.user comes from a POST Form variable it must not have the id that passport.serialize would've stored in the cookie.
You should instead use the new values from req.body.user and update req.user itself, and then do req.login(req.user, ...
var _ = require('lodash');
module.exports = function(req, res, next) {
//using lodash merge the updated user into the cached user
_.merge(req.user, req.body.user);
req.login(req.user, function(err) {
if (err) return next(new Error('Error updating user profile'));
console.log('USER UPDATED *******', req.user);
next();
});
};

Access to "req" Object in Supertest After a Response

Is there any way to directly access the req object in supertest, while/after the request is being tested? I want to test my passport strategies, so I want to check req.user, req.session, and perhaps others. I know I can test page redirects or flash, as those are what my strategies do, but it seems useful to see if there is a user on the req object, as well. If I do this, I can also check how many users there are at any one time.
I will sign users up with the "local-signup" strategy, which is defined thusly:
'use strict';
// get passport & mongoose
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// signup function
passport.use('local-signup', new LocalStrategy({
passReqToCallback: true // pass the entire request to the callback
},
function(req, username, password, done) {
process.nextTick(function() {
// find a user with the same username
UserModel.findOne({username: username}, function(err, user) {
// if there is an error, log it then return it
if(err) {
console.log("Error finding a user in the database: " + err);
return done(err);
}
// if a user was already found
if(user) {
return done(null, false, "User already exists");
}
// if we get this far, create a new user from the request body
var newUser = new UserModel(req.body);
// save it and sign it in
newUser.save(function(err) {
if(err) {
console.log("Error during signup: " + err);
return done(err);
}
return done(null, newUser);
});
});
});
}
));
};
One way I use this strategy is like this:
My "local" strategy is defined like this:
'use strict';
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// create our local passport strategy & use it
passport.use(new LocalStrategy({
// use the default names for the username & password fields
usernameField: 'username',
passwordField: 'password'
},
// main strategy function
function(username, password, done) {
// find user with given username
UserModel.findOne({
username: username
},
// with this username, do this
function(err, user) {
// if there's an error, log it then pass it along
if(err) {
console.log("Error during login: " + err);
return done(err);
}
// if the username and/or password is incorrect, return an error
// along with a message
if(!user || !user.authenticate(password)) {
return done(null, false, {
message: 'Invalid username and/or password'
});
}
// if everything is correct, return the user document from the database
return done(null, user);
});
}
));
};
I use both strategies like this, for example:
app.route(pageName).post(function(req, res, next) {
passport.authenticate(strategyName, function(err, user, info) {
if(err || !user) {
res.status(401).send(info);
}
else {
req.login(user, function(err) {
if(err) {
res.status(400).send(err);
}
else {
res.send(null);
}
});
}
})(req, res, next);
});
I tried
request = require('supertest');
this.authServer = require('../my-server');
request(this.authServer)
.put('/signup')
.set('Content-Type', 'application/json')
.set('Host', 'konneka.org')
.send(this.fullUser)
.end(function(req, res, done) {
console.log(res);
});
The res object I logged, inside the end() function, which was way too long to show here, has a req object defined on it, but it seems to only have the objects & functions that were defined before the request was opened. In other words, it does not have req.user, req.session, or other objects I want, because they are defined after the request completes and a new request is started. I noticed it has status codes, as well, which are only defined after the request completes, so I must be missing something.
Is there any way to get access to the req object after the request you are testing is ended? Or am I going about this completely the wrong way?
You cannot do what you want using supertest.
Not sure if this helps but I'll add a little context to clarify the answer:
supertest is a wrapper on top of superagent (client side) with some basic hooks into express to start up the HTTP listener. Under the hood, it really is not any different from starting up your express app, waiting for it to listen on a port, making an HTTP request to that port, and parsing the result. In fact, that is exactly what it does.
So essentially supertest only has access to what ever your client would have access to (a browser or some API client). In other words, if it isnt in the HTTP response body, you wont have access to it. req.user and req.sesssion are server side state variables that are (most likely) not in the response (unless you are doing something strange).
If you want to test in exactly the way you describe, you will have to use some alternative strategy of testing, not supertest.
I found this question when I thought I wanted to do this, and for me it worked well to check the status of the user created by the request instead of verifying the content of the req object. You do have access to the full database where I assume you users somehow ends up.

Update logged in user details in session

I am using PassportJS with ExpressJS.
I need to update the logged in user details. While I do update this in the DB, how do I update it in the session too so that request.user contains the updated user details?
That is, after updating the database, how do I update the session info on the user as well?
I tried directly assigning the updated details to request.user but it did not work.
I then tried request.session.passport.user - this worked but there is a delay of around 5 to 10 seconds before it gets updated in request.user too.
Is there a function that I need to call that updates the user information stored in the session? Or is there some other object that I can update where the change does not have a delay
I've been hunting down an answer for this too. Never mentioned in any docs or tutorials!
What seems to work is, after saving your newly updated user, do req.login(user)...
// "user" is the user with newly updated info
user.save(function(err) {
if (err) return next(err)
// What's happening in passport's session? Check a specific field...
console.log("Before relogin: "+req.session.passport.user.changedField)
req.login(user, function(err) {
if (err) return next(err)
console.log("After relogin: "+req.session.passport.user.changedField)
res.send(200)
})
})
The clue was here... https://github.com/jaredhanson/passport/issues/208
User.findById(req.user._id,function(err,doc){
req.logIn(doc,function(err1){
if(err1){ console.log("Error : "+err1) }
else{
res.render("abc.ejs",{user:req.user});
console.log('Item Removed Successfully!!!');
}
});
});
Here we are re-login the user
User => Mongoose Model
I had similar problem today and decided to share my findings, since i couldn't find similar answer.
The problem was that (copied from passport documentation) i was getting the user data directly from the token, that the user sent in the request. Which was of course outdated.
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : CONFIG.JWT_SECRET
},
function (jwtPayload, cb) {
return cb(null, jwtPayload);
}
));
while i should get the fresh user object from the database instead:
return User.findById(jwtPayload.id)
.then(user => {
return cb(null, user);
})
.catch(err => {
return cb(err);
});

Resources