Why won't my express-session persist? - node.js

I'm currently working on a MERN stack application that will utilize a login/sign-up form that is linked to my Mongo database. I have successfully been able to create a user, hash the user's password, and then store this user into the database, as well as log the user in by checking their credentials from the database.
When attempting to employ a solution that's industry standard, I decided to work with Passport and Express-session to implement authentication and sessions for each user when they log in. Once a user is created/logged in, passport correctly stores their information into req.user, and a session is created and logged with req.session. Below is the console log of req.user followed by req.session :
user with id xxx serialized
email: ...
password: ...
isAuthenticated: true
Session {
path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {user: xxx } }
This all displays right after I log-in, and to my knowledge this means that a session has been created and my user has been serialized properly. However, when I go to a new page on the webapp, I lose my session-- req.user becomes undefined, and req.sessionId changes each time the page does -- which leads me to believe that a new session with an empty user is created each time I switch pages (I am running a fetch method in my NavBar in React that triggers each time the Component mounts). Google Chrome also does not accurately log the cookies even when they are created as evidenced by the console logs.
My question is : why won't my session persist when I switch pages? My goal is to keep track of this session data to pass to my React frontend to render specific content. Is there a better way to go about this? Below is my middleware ordering:
app.use(session({
secret: 'test',
saveUninitialized: false,
resave: true
}));
app.use(passport.initialize());
app.use(passport.session());
Here are the methods I am calling in my router file:
router.get('/',function(req, res){
console.log(req.user);
console.log(req.session);
});
router.post('/login',passport.authenticate('local'), function(req,res)
{
console.log(req.user);
console.log(req.isAuthenticated());
console.log(req.session);
});
passport.serializeUser(function(user, done) {
console.log("user with id " + user._id + " serialized");
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializing user with id " + id + " ");
User.findById(id, function(err, user) {
done(err, user);
});
});
Here is my api fetch call from my front end:
componentDidMount(){
var current_user = "";
fetch("http://localhost:3001/users/", {
method: "get",
headers: {
'Accept':'application/json',
'Content-Type': 'application/json'
}
})
.then( (response)=> response.json())
.then( (response)=> {
if(response.user!==current_user){
current_user = response.user;
this.setState({
user: current_user
}), ()=> console.log(response);
}
})
}
I am also aware that currently the fetch method will not properly work: the goal is for that skeleton to work properly and have it return the correct id. What is logging improperly is the session and the req.user before I can even get the chance to pass it to my front end. Any and all help would be appreciated.

Related

Cookies persist in supertest/superagent test, but the user doesn't stay logged in

My Goal
I'm trying to use supertest's agent function in a jest beforeEach() to login the user before each test, as I want each test to run under the assumption that the user is signed in. For authentication, I am using passport and passport-local.
This is what I tried (with parts cut out for brevity):
Test file:
import { agent, SuperAgentTest } from 'supertest';
import app from '../../src/app';
// create a `testRequest` variable to use in the tests
// that will be refreshed in between
let testRequest: SuperAgentTest;
const fakeUser = { email: 'john#john', username: 'john', password: 'john' };
beforeEach(async () => {
// create new agent
testRequest = agent(app);
// register and login
await testRequest.post('/register').send(fakeUser).expect(302);
// other irrelevant stuff...
});
// protected route
describe('POST /campgrounds/new', () => {
it('returns 200 OK', () => {
return testRequest.get('/campgrounds/new');
})
});
/register route:
router.post('/register', async (req, res) => {
const { password, ...details } = req.body;
try {
// I am using passport-local-mongoose for this function-
// it just registers the user
const user = await User.register(new User(details), password);
req.login(user, (err) => {
// error handling and redirect
});
} catch (e) {
// error handling
}
})
This is my result
Instead of a 200 status, I get a 302 status, meaning I was redirected to the login page. To debug this, I created a test route called /current which will log the current user and session ID cookie. I then sent a GET request to this route in both the it and beforeEach function respectively.
Interestingly, they both logged the same session ID, but only the request in beforeEach had a user object attached to the request.
#1 Ensure body parser correct order
Make sure you have this before any routes or auth-related things.
app.use(express.json())
#2 Check Passport Middleware Wire-up
Ensure you call app.use(passport.initialize()) & app.use(passport.session()) before any app.use('/', aRouter), router.get, router.post, etc:
// Set up session w/ specific config
app.use(session({
secret: 'bquyqueajhbd',
resave: true,
saveUninitialized: true,
store: new FileStore({path: '/tmp/session'})
}));
// Wire up the
app.use(passport.initialize())
app.use(passport.session())
EDIT: Notes on req.user
Passport is designed to store the user ID in session.
Every request to the server must reload the user from the database.
This is the job of the middleware passport.initialize() and passport.session().
The logic there will call passport.deserializeUser to lookup the user by ID - the same ID that was saved upon login into the session by passport.serializeUser.
passport.serializeUser(function(user, done) {
done(null, user.id); // <-- Here's where the ID is saved to session.
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user); // <-- Here is where the `req.user` get's it's value from.
});
});
To debug this I'd focus on the passport.deserializeUser callback, add logs before and after the DB query.
(Note: it's been a few years since I taught this. Appologies if I'm not using the precise terms, etc.)

Passport-SAML: read user information

Still a noob!
I am working on to build a Node application, and I have already setup various required end points. One of the requirements for my project is to use authentication using SAML mechanism. I am using passport-SAML for authentication in my application.
So far, I have been able to setup and use SAML strategy, and my application is able to call the idp entry point, and receive the response back from Idp.
I am unable to understand how do we access the user information returned by idp, so that I can use the SAML returned user information to create and maintain sessions.
const saml = require('passport-saml');
module.exports = function (passport, config) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
var samlStrategyOptions = new saml.Strategy(
{
// URL that goes from the Identity Provider -> Service Provider
callbackUrl: config.passport.saml.callback_url,
// path: config.passport.saml.path,
// URL that goes from the Service Provider -> Identity Provider
entryPoint: config.passport.saml.entryPoint,
issuer: config.passport.saml.issuer,
identifierFormat: null,
// Service Provider private key
decryptionPvk: config.passport.saml.decryptionPvk,
// Service Provider Certificate
privateCert: config.passport.saml.privateCert,
// Identity Provider's public key
cert: config.passport.saml.cert,
validateInResponseTo: false,
disableRequestedAuthnContext: true
},
function (profile, done) {
return done(null,
{
id: profile.uid,
email: profile.email,
displayName: profile.cn,
firstName: profile.givenName,
lastName: profile.sn
});
})
// module.exports.samlStrategyOptions = samlStrategyOptions ;
passport.use(samlStrategyOptions);
};
Following are my route controllers for express
router.route('/login')
.get(
passport.authenticate(config.passport.strategy,
{
successRedirect: '/',
failureRedirect: '/login'
})
);
router.route('/login/callback/')
.post(
passport.authenticate(config.passport.strategy,
{
failureRedirect: '/',
failureFlash: true
}),
function (req, res) {
res.redirect('/');
}
);
And this is a SAML snippet of properties that I recieve in response from Idp.
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">Shubham123</saml:NameID>
I was getting the same. SO I have used body-parser as middleware
// middleware to parse HTTP POST's JSON, buffer, string,zipped or raw and URL encoded data and exposes it on req.body
app.use(bodyParser.json());
// use querystring library to parse x-www-form-urlencoded data for flat data structure (not nested data)
app.use(bodyParser.urlencoded({ extended: false }));
and then you will get the profile like
{ issuer: '',
sessionIndex: '_x0P5ZeWx-ACSQAulKgVTxSquNsVdac_H',
nameID: 'auth0|5a266569083226773d5d43a9',
nameIDFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
nameQualifier: undefined,
spNameQualifier: undefined,
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'auth0|s9ds',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'myuser#q.com',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'myuser#q.com',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn': 'myuser#q.com',
'http://schemas.auth0.com/identities/default/provider': 'auth0',
'http://schemas.auth0.com/identities/default/connection': 'Username-Password-Authentication',
'http://schemas.auth0.com/identities/default/isSocial': 'false',
'http://schemas.auth0.com/email_verified': 'false',
'http://schemas.auth0.com/clientID': 'bZVOM5KQmhyir5xEYhLHGRAQglks2AIp',
'http://schemas.auth0.com/picture': 'https://s.gravatar.com/avatar/e85e57405a82225ff36b5af793ed287c?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fsu.png',
'http://schemas.auth0.com/nickname': 'myuser',
'http://schemas.auth0.com/identities': '[object Object]',
'http://schemas.auth0.com/updated_at': 'Mon Dec 18 2017 12:14:28 GMT+0000 (UTC)',
'http://schemas.auth0.com/created_at': 'Tue Dec 05 2017 09:22:49 GMT+0000 (UTC)',
getAssertionXml: [Function] }
and create user by extracting data like
{ id: profile["nameID"], userName: profile["http://schemas.auth0.com/nickname"] }
In order to get the user details, in your IDP's console, you have to setup the parameters in SP settings which you want the IDP to return and you'll get them in the assertion.
This is what I did in onelogin:
I'm using the node-saml passport module and I found this example very useful.
To summarize, once the SAML process is resolved, (your IdP is making a POST callback to your handler), the user data is stored in the request object. Now, if you want to get that user data, for example in any GET request, you could do the following:
app.get('/logout', function(req, res) {
console.log('logout');
console.log(req.user);
req.logout();
res.redirect(config.passport.saml.logoutCallback);
});
Where req.user contains all your user data. In the example, req.user contains the following:
{
firstName: 'local givenName',
lastName: 'local lastname',
email: 'testUser#sample.com'
}

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.

req.session.passport and req.user blank , and req.isAuthenticated returns false after initial successful login using passport-facebook

After the initial successful login from Facebook and login redirection callback using passport-facebook 1.0.3 and express 4.6.1,req.session.passport and req.user contains the value set during the serialize call(which i get from the stragegy) , but on subsequent visits to different routes on the site the req.session.passport and req.user is blank , and req.isAuthenticated() returns false , so after the initial successful login from FB the ensureAuthentication method on all other routes fails .
I'm not using a cluster setup , so i believe the memory store is enough to handle this , the express configuration looks fine(i mean the order) , here is my express configuration
configExpressApp.set('views', './views');
configExpressApp.set('view engine', 'jade');
configExpressApp.use(morgan('dev'));
configExpressApp.use(cookieParser());
configExpressApp.use(bodyParser.urlencoded({
extended: true,
}));
configExpressApp.use(bodyParser.json());
configExpressApp.use(expressSession({
secret:'MyExpressSecret',
saveUninitialized: true,
resave: true
}));
configExpressApp.use(passport.initialize());
configExpressApp.use(passport.session());
configExpressApp.use(methodOverride());
configExpressApp.use(express.static('./public'));
Here is the req.session object on the initial successfull login and redirection ,The req.user contains the same data as the req.session.passport.user
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport:
{ user:
{ _id: 53ce23e3121421f229a438f8,
info: false,
loginType: 'fb',
fbId: 'MyId',
name: 'Karthic Rao',
email: 'kartronics85#yahoo.com'
}
}
}
this is the information i associated earlier with the done() callback inside the strategy and also inside the serialization call . After the successfull login and callback, i use res.redirect to redirect the user to a different route , but the requests coming from that route contains the sessionID (so i dont think its the issue with the session store), but the req.user field doesnt exist(may be because passport.initialize() and passport.session() middlewares dont find the request to be authenticated) and the req.session.passport field is empty , here are the details from the console.log of the req object .
sessionID: 'I9R1c3PIYgDW5OpWbNT7qb02Hn4lOeAB',
session:
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {} },
Here is my deserialize method
passport.deserializeUser(function(user, done) {
console.log('deserialize loginType facebook');
db.collection("users").findOne({
fbId: user.id
}, function(err, docs) {
console.log(docs);
done(err, docs);
});
});
Here is my serialize method
passport.serializeUser(function (user, done) {
console.log(user);
done(null, user);
});
This is creating a great hindrance to my development , how can i sort this out ??
Well, if you could add the code where you use your strategy (it should be in the middleware), that would give us a complete view of the problem, because we need to know what object is sent to serializeUser.
(Very) basically, when a user tries to authenticate, things happen that way :
Passport tries to authenticate your user against Facebook servers
If it succeeds, the callback function (or successRedirect) is called
with an object containing the user's details
Then, Passport makes a call to req.login to store some info about
the user in session
Then serializeUser is called and effectively store the data you
specified in session.
BUT from the code you posted, I suspect that in your deserializeUser, user.id is undefined, because the user object that is stored in session uses an ID field called _id and not id.
If you change
db.collection("users").findOne({
fbId: user.id
to
db.collection("users").findOne({
fbId: user._id
db.collection("users").findById(user._id, [fields],[options],[callback]);
I think it should work.
EDIT : I edited my answer based on #BrandonZacharie's remark, which pointed out an error in my code.

Express.js/Passport app creating new session for each request, despite session ID in request headers

This has not been noticed before, because for client-server communications, nothing requiring authentication is happening via XHR (as of yet). In implementing some integration tests, I am creating realy requests node-xmlhttprequest to a real instance of the application. User is being authenticated in the first request, and in theory the user's unique identifier and some other pertinent information is stored in session (as I've said, this works just fine for real clients, who do not need to confirm their identity over XHR). For some reason, even though subsequent requests are being fired with the exact same session ID, I am not able to retrieve the session in subsequent requests, and thus can't see that the user is authenticated, and this leads to failing tests where the expected behaviour does not happen, and HTTP 401 UNAUTHORIZED is filling up the terminal.
I've seen a couple of questions which look sort of like this, but that "same session ID" thing does not seem to be present among them.
Do I need to manually add Set-Cookie headers to all XHR requests? That's terrible, there's got to be a better way!
So people like source code, here's some from the test, firing these requests:
// Ensure logged in before sending request, to verify that authorized
// users encounter the expected behaviour.
jQuery.post(domain+'/login', {email:'test#express.app', password:'12345678'},
function(data,x,y){
data.should.equal("true")
jQuery.ajax({url:domain+'/event', type:"POST",
name: "TestEvent", location: "TestVille",
startDate: new Date('2013-09-01'), startTime: '6:45 pm',
description: "Test Event #1", success: state.success,
error: state.error, completed: state.completed
})
})
Sign in happens, user ID gets written into session (`req.logIn() should do this, and it does not report any failures), serialization of the user ID does not report any failures, and I am extremely confused as to why subsequent requests using the same session ID are unable to find the serialized user ID.
I am generally not involved with web development, so this may be obvious to some people, but I've been searching for an answer all day and have simply not been able to find one. I'd appreciate any pointers, and am happy to provide as much code as I'm able to, to illustrate what the problem is.
A few additional points of code which may be pertinent:
Serialization/deserialization of user ID (currently implemented in the simplest possible
manner -- This is very early in the initiallization of middleware, after initiallizing passport and passpot.session(). And this works perfectly well for non-XHR requests)
// Serialize user for passport session-store
// Currently only serializing 'user_id'
passport.serializeUser(function(user, done) {
done(null, user._id)
})
// Deserialize user from session-store to provide
// access to the User instance
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user)
})
})
Authentication of users via the local strategy:
passport.use(new LocalStrategy({
usernameField: "email", passwordField: "password",
passReqToCallback: true, failureFlash: true
},
function(req, email, password, done) {
User.findByEmail(email, function(err, user) {
if(user) {
if(err) console.log(err)
if(user instanceof UserLocal) {
user.verifyPassword(password, function(err, match) {
if(err) console.log(err)
if(!match) {
return done(err, false,
"Username or password is incorrect.")
}
return done(null, user)
})
} else {
var msg = "Account registered under " + user.providerName()
+ ", please login using " + user.providerName()
return done(err, false, msg)
}
} else {
return done(err, false, "Username or password is incorrect.")
}
})
})
And finally the requests which write to session in the first place:
function loginHelper(req, res, next) {
passport.authenticate('local', {
failureFlash:true,
failureRedirect: false,
successRedirect: false
},
function(err, user, info) {
req.logIn(user, function(err) {
if(!err) err = {}
if(req.xhr) {
console.log(req.session)
res.status(200).end(err.message || "true")
} else {
if(err.message) req.flash('error', err.message)
else res.redirect('/')
}
})
})(req, res, next)
}
I know there are some weird things like sending a status of 200 regardless of the login status, but I can confirm that the serialized user id is written to session on the initial login XHR request, and that it's not deserialized on subsequent XHR requests.
As I believe I mentioned, I am relatively inexperienced in this area, and could very much use a boost. Any assistance whatsoever would be much appreciated, thanks in advance.
That's help me
First
app.use(express.static(__dirname + '/public', { maxAge: oneDay }));
and update functions to do not trigger database
passport.serializeUser( (user, done) => {
var sessionUser = { id: user.dataValues.id, fio: user.dataValues.fio, email: user.dataValues.localemail, role: user.dataValues.role, login: user.dataValues.login, position: user.dataValues.position }
done(null, sessionUser)
})
passport.deserializeUser( (sessionUser, done) => {
// The sessionUser object is different from the user mongoose collection
// it's actually req.session.passport.user and comes from the session collection
done(null, sessionUser)
})
https://www.airpair.com/express/posts/expressjs-and-passportjs-sessions-deep-dive

Resources