Node.js Express + Passport + Cookie Sessions n > 1 servers - node.js

From the Passport docs:
In a typical web application, the credentials used to authenticate a
user will only be transmitted during the login request. If
authentication succeeds, a session will be established and maintained
via a cookie set in the user's browser.
Each subsequent request will not contain credentials, but rather the
unique cookie that identifies the session. In order to support login
sessions, Passport will serialize and deserialize user instances to
and from the session.
So, being that the authentication is maintained in the client's browser, I should be able to scale the web servers to > 1 (each server uses same database) and have Passport deserialize user from an ID stored in a cookie regardless if the server has seen the user before. If the server has seen the user before but no cookie exists, I would expect Passport to fail the authentication...but I can't even get that far with n > 1 servers.
With 1 server process, Passport works fine:
The server is more or less verbatim of docs on both servers (after much trials w/ connect references and such):
app.configure(function() {
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
When a user login is successful, right before I return the HTTP request, I log req.user and req.session:
console.log(req.user);
console.log(req.session);
res.send(200, req.user);
// {"cookie":{
// "originalMaxAge":null,
// "expires":null,
// "httpOnly":true,
// "path":"/"
// },
// "passport":{"user":"ID"}}
When the HTTP request returns, the web app requests / and expects the server(s) to set req.user with ID ID from the cookie passed:
passport.deserializeUser(function(req, user_id, cb) {
console.info('Passport deserializing user: '
.concat(user_id));
console.log(req.user);
console.log(req.session);
// ...
// cb(null, user);
});
With 1 server running, this logs as:
Passport deserializing user: ID
undefined
undefined
Immediately after the callback cb(null, user) and before I return the request, I log req.user and req.session and see what I would expect!
Now, with n > 1 servers running, let's say 2, if the web app requests / from server 2 and expects it to set req.user with ID ID from the cookie passed (originally set by logging into server 1), I don't even see the deserialize function called! When I added logging to the beginning of the HTTP route (after middleware):
console.log(req.user);
console.log(req.session);
I see:
undefined
// {"cookie":{
// "originalMaxAge":null,
// "expires":null,
// "httpOnly":true,
// "path":"/"
// },
// "passport":{}}
...which tells me something is up with the cookie, but what? Both server processes are on Heroku behind the same DNS. I have read various discrepancies about needing some "shared session" store for this...but that's what a cookie is at the client's browser level. Please help! Express is the latest 3.X and Passport is 2.1.

As an alternative to having a server side database, you can store the session data in encrypted cookies, such as those used by Mozilla: https://github.com/mozilla/node-client-sessions. That is more scaleable than any server side database but you have to keep the size of the cookie reasonably small. If I had a bit more time I'd write you a demo but for now I'll just put this down as a question to come back to.

The cookie does not contain session data, it contains just the session ID. Using this ID your Express application will get the related session data from it's session store. And since you are not defining a store when calling app.use(express.session({ secret: 'keyboard cat' })); you are using the built-in memory store which is not shared between processes.
So, install any session store that uses a database, eg. connect-mongo for MongoDB.

Related

Why use cookie-session in addition to passport.js?

My understanding of passport.js so far is that passport.js serializes the user object and sends an ID every time to the client. I am just starting with it so sorry if it's a silly question:
Instead of express-session, I am using cookie-session as I am a beginner. My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed.
Now, I don't understand why we can't just use the passport.js ID? Why do we need to use cookie-session in addition? Also, (this may be a little unrelated, but) is the difference between session-based authentication and token-based authentication that this ID that's shared is dynamic, or changing every time? Is this still the standard and modern way of doing it in 2020?
"Instead of express-session, I am using cookie-session as I am a beginner."
using cookie session does not make anyone beginner. If you are going to store large data, use express-session, cause it stores the data in the database or redis, keeps the database id of that data, so when it gets a request, fetch the database with that id and compare the request credentials. On the other hand, cookie-session stores the data upto 4kb in the cookie on the user browser and since only user-id is stored in the cookie with passport.js, generally cookie session is used.
passport.serializeUser(
(user, done ) => {
done(null, user.id); // stores the id<4kb
}
);
When client authorizes your app, google send the responds to your callback url.
app.get("/auth/google/callback", passport.authenticate("google"))
passport.authenticate() will call req.login() this is where passport.user gets generated. req.login() will initiate the serializeUser() which determines which data of the user should be stored in the session.
passport:{user:userId}
Now this object will be assigned to req.session. so we will have req.session.passport.user
Everytime when you make a request to a server, browser automatically checks if there is cookie set related to that server and if there is it automatically attaches the cookie to the request. If you were using token based authentication, you had to manually attach the cookie to request everytime you make a request. Cookie is just transportation medium, you store data and move the data, you can even store the token in cookie. Cookie is not just related to authentication. If you have server-side project, you have to set cookie.(this is just a side node).
"My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed."
so far I explained how session is created. Now what happens when user makes a request?. In app.js file you should have two middleares.
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.initialize()) this function checks if req.session.passport.user exists, if it does it will call passport.session(). if it finds a serialized user object in the session, it will consider this req is authenticated. And then deserializeUser() will be invoked. it will retrieve the user and attach it to req.user
You don't need to use session. It is totally upto you. Just put {session: false} in route. You don't need to write passport.serializeUser and passport.deserializeUser.
cookie-session puts cookie on client system, and it is sent each time with request. passportjs search that cookie and run deserializeUser to convert it into object and attach it with request object.
express-session stores session data on the server; it only saves the session identifier in the cookie, not session data.
where as cookie-session is basically used for lightweight session applications. it allows you to store the session data in a cookie but within the client [browser]. Only use it when session data is relatively small and easily encoded as primitive values See this question for more understanding
const express = require('express');
const { Router } = express;
const router = new Router();
router
.get('/', passport.authenticate('google', { session: false }))

Save data in express session

I would need to save some token in express session. So, I would need help how to save this token in session object.
Any example would be more helpful.
Also is it a good practice to save such information in session object or do I need to use some persistent storage like redis cache DB.
Yes, you can store a token in the session. This is generally done as follows:
app.use(session({
token : your_token_value
})
}));
Or, as an alternative way:
app.get('/', function(req, res, next) {
var sessData = req.session;
sessData.token = your_token_value;
res.send('Returning with some text');
});
Regarding the storage place. It is a kind of a different layer under the session. The values which you store in the session can be placed in different locations: in the application memory, in memcache, a database or in cookies.
For production you can use Memory Cache. For instance, https://github.com/balor/connect-memcached:
It can be achieved as follows:
app.use(session({
token : your_token_value,
key : 'test',
proxy : 'true',
store : new MemcachedStore({
hosts: ['127.0.0.1:11211'], //this should be where your Memcached server is running
secret: 'memcached-secret-key' // Optionally use transparent encryption for memcache session data
})
}));

What does passport.session() middleware do?

I am building an authentication system using Passport.js using Easy Node Authentication: Setup and Local tutorial.
I am confused about what passport.session() does.
After playing around with the different middleware I came to understand that express.session() is what sends a session ID over cookies to the client, but I'm confused about what passport.session() does and why it is required in addition to express.session().
Here is how I set up my application:
// Server.js configures the application and sets up the webserver
//importing our modules
var express = require('express');
var app = express();
var port = process.env.PORT || 8080;
var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var configDB = require('./config/database.js');
//Configuration of Databse and App
mongoose.connect(configDB.url); //connect to our database
require('./config/passport')(passport); //pass passport for configuration
app.configure(function() {
//set up our express application
app.use(express.logger('dev')); //log every request to the console
app.use(express.cookieParser()); //read cookies (needed for auth)
app.use(express.bodyParser()); //get info from html forms
app.set('view engine', 'ejs'); //set up ejs for templating
//configuration for passport
app.use(express.session({ secret: 'olhosvermelhoseasenhaclassica', maxAge:null })); //session secret
app.use(passport.initialize());
app.use(passport.session()); //persistent login session
app.use(flash()); //use connect-flash for flash messages stored in session
});
//Set up routes
require('./app/routes.js')(app, passport);
//launch
app.listen(port);
console.log("Server listening on port" + port);
passport.session() acts as a middleware to alter the req object and change the 'user' value that is currently the session id (from the client cookie) into the true deserialized user object.
Whilst the other answers make some good points I thought that some more specific detail could be provided.
app.use(passport.session());
is equivalent to
app.use(passport.authenticate('session'));
Where 'session' refers to the following strategy that is bundled with passportJS.
Here's a link to the file:
https://github.com/jaredhanson/passport/blob/master/lib/strategies/session.js
And a permalink pointing to the following lines at the time of this writing:
var property = req._passport.instance._userProperty || 'user';
req[property] = user;
Where it essentially acts as a middleware and alters the value of the 'user' property in the req object to contain the deserialized identity of the user. To allow this to work correctly you must include serializeUser and deserializeUser functions in your custom code.
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (user, done) {
//If using Mongoose with MongoDB; if other you will need JS specific to that schema.
User.findById(user.id, function (err, user) {
done(err, user);
});
});
This will find the correct user from the database and pass it as a closure variable into the callback done(err,user); so the above code in the passport.session() can replace the 'user' value in the req object and pass on to the next middleware in the pile.
From the documentation
In a Connect or Express-based application, passport.initialize()
middleware is required to initialize Passport. If your application
uses persistent login sessions, passport.session() middleware must
also be used.
and
Sessions
In a typical web application, the credentials used to authenticate a
user will only be transmitted during the login request. If
authentication succeeds, a session will be established and maintained
via a cookie set in the user's browser.
Each subsequent request will not contain credentials, but rather the
unique cookie that identifies the session. In order to support login
sessions, Passport will serialize and deserialize user instances to
and from the session.
and
Note that enabling session support is entirely optional, though it is
recommended for most applications. If enabled, be sure to use
express.session() before passport.session() to ensure that the login
session is restored in the correct order.
While you will be using PassportJs for validating the user as part of your login URL, you still need some mechanism to store this user information in the session and retrieve it with every subsequent request (i.e. serialize/deserialize the user).
So in effect, you are authenticating the user with every request, even though this authentication needn't look up a database or oauth as in the login response. So passport will treat session authentication also as yet another authentication strategy.
And to use this strategy - which is named session, just use a simple shortcut - app.use(passport.session()). Also note that this particular strategy will want you to implement serialize and deserialize functions for obvious reasons.
It simply authenticates the session (which is populated by express.session()). It is equivalent to:
passport.authenticate('session');
as can be seen in the code here:
https://github.com/jaredhanson/passport/blob/42ff63c/lib/authenticator.js#L233

ExpressJS creates a new session on each page change

I'm trying to implement express and passport sessions like so:
app.use(connect.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
cookie: {
path: "/",
httpOnly: true,
maxAge: null
},
store: redisStoreConnect,
secret: "something",
key: 'pksy.sid'
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser (user, done) ->
done null, user.email
return
passport.deserializeUser (email, done) ->
User.findOne
email: email
, (err, user) ->
done err, user
return
return
If I navigate to a page in my site, a new session is created and stored in redis. If I refresh that page, the session appears to persist. If I navigate to a new page or even if I close the tab and reopen to the same page, a new session is created.
This is especially frustrating since passport only authenticates the session that was generated for my login page. How do I get my sessions to persist across pages?
Update: Thanks #robertklep for reminding me to check what cookie the browser was actually sent back (which I should have done to start). It turns out the browser is sending back the right cookie, and the authentication is working. The sessions are in fact persisting, but for some reason a new session (unused by the browser) get's created with each page request. How can I stop this?
"Oh, you didn't know the browser doesn't send the session cookie with the request for the favicon?" says my roomate the hacker, whose ilk created the need for the following single line of code.
11 hours of debugging later here is what solved the problem:
app.use express.favicon()
Express was treating the favicon like any other resource, while the browser was making a specialized, apparently cookieless request for it. Express assumed this request must have come from a client without a session cookie, so it created a new session for them; a session never to be called upon again. express.favicon() saves the day!
In my case I have to use
app.use(passport.initialize());
app.use(passport.session());
before
app.use(app.router);
Hope this can help.

Working with Sessions in Express.js

I need help understanding the concept of sessions for a web application. I am running a Node.js server with Express 3.0.
My goals are to:
Create a session for each user that logs in
Store this session and use it for validating if the user is already logged in (prevent two devices using the same user at the same time) and to limit access to certain pages (by matching session ID to some other data)
I will be using MemoryStore to save the sessions (seems easiest). If the above goals make sense can you provide a thorough explanation of how to achieve them?
Express has nice examples in the github repo. One of them deals with authentication and shows how to attach the user to the req.session object. This is done inside the app.post('/login') route.
To limit access to certain pages add a simple middleware to those routes
function restrict(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
app.get('/restricted', restrict, function(req, res){
res.send('Wahoo! restricted area, click to logout');
});
As Brandon already mentioned you shouldn't use the MemoryStore in production. Redis is a good alternative. Use connect-redis to access the db. An example config looks like this
var RedisStore = require('connect-redis')(express);
// add this to your app.configure
app.use(express.session({
secret: "kqsdjfmlksdhfhzirzeoibrzecrbzuzefcuercazeafxzeokwdfzeijfxcerig",
store: new RedisStore({ host: 'localhost', port: 3000, client: redis })
}));
Use MemoryStore in express ONLY if you are not creating multiple instances (such as with the cluster module). If you are load balancing across machines, your load balancer will need to use sticky / persistent sessions.
If you meet those requirements, then all you need to do is upon login, once the credentials are validated, set a session variable to indicate logged in, for example:
req.session.loggedIn = true;
If you want to check if a user is logged in, simply check that variable.
if (req.session.loggedIn) {
// user is logged in.
}
else {
// user is not logged in.
}
You mentioned preventing a single user from having sessions more than one session at a time. To achieve that, you may need to store something in a database indicating that the user is logged in. I warn you, this can be dangerous because of stale sessions. For example, what if a user logs in, but never logs out? What if they close their browser window so the session is gone forever?
Express has no concept of an idle session expiration. I have implemented such a thing by storing all sessions in the database along with a last accessed timestamp and then periodically clean up the session data based on the time. But then you need to update your list of who is logged in as well.

Resources