What does passport.session() middleware do? - node.js

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

Related

How can I use the same Express session for multiple hostnames?

So I have a Node.js + Express app I can access with a couple of hostnames: example1.com and example2.com (they are not subdomain! I know how to do it if they are subdomains).
I use Express Session with MongoDB Store and passport to manage authentication.
I'd like to use the same session with both the domains but I can't
figure out how to do it.
Each time I access the two domains it creates two different sessions and I cannot understand where I could check if the session is already up and use that for both the domains.
Basically this is my current express init file (I removed a lot of things, just to focus on sessions):
'use strict';
/**
* Module dependencies.
*/
var express = require('express'),
bodyParser = require('body-parser'),
session = require('express-session'),
cookieParser = require('cookie-parser'),
passport = require('passport'),
MongoDBStore = require('connect-mongodb-session')(session),
config = require('./config');
module.exports = function(db) {
// Initialize express app
var app = express();
// CookieParser should be above session
app.use(cookieParser());
var store = new MongoDBStore(
{
uri: config.db,
collection: config.sessionCollection
});
// Express MongoDB session storage
app.use(session({
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
store: store,
}));
app.use(passport.initialize());
app.use(passport.session());
return app;
};
Any hint?
IMO the only way to do that is, if you are able to figure out on server side that the request is coming from the same browser/machine. Because, browser would not the share the session Id cookie(as the requests are for two different domains) and on server you would think it of as a new request for session, and you would always grant a new sessionId to the client.
If you really want to have single session store, I recommend writing your own session store with the below idea:
Firstly, don't use cookies for session, and use browser's localstorage and sign the sessionId from the server that needs to be stored on the client, which protects it from being tampered. This session Id would be included in the requests using your AJAX and client-side JS in each requests acting as a sessionId cookie.
Secondly, Open an iframe of the other domains when ever a domain is opened, let say if example1.com is opened you should open an iframe of example2.com and vice-versa. In your iframes write code to send signed localstorage information back to parent frame using window.postMessage() Since, you are owner of all the domains you should be able to allow window.postMessage() communication across your domains.
Since, you would receive the same session Id in each request across all domains you can detect unique session across domains and devise session store on server side to store only one entry across domains.

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

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.

PassportJS - Using multiple passports within Express application

Suppose I need two different passports within one express application (e.g. user & room).
So I define two separate vars:
var passport = require('passport');
var roomPassport = require('passport');
then, I initialize them with separate passport strategies:
require('./config/passport')(passport); // pass passport for configuration
require('./config/roompassport')(roomPassport); // pass passport for configuration
the last step is to set them as Express middleware:
// required for passport
application.use(session({ secret: 'ilovepassport;-)' })); // session secret
application.use(passport.initialize({ userProperty: "user" }));
application.use(passport.session()); // persistent login sessions
application.use(roomPassport.initialize({ userProperty: "room" }));
application.use(roomPassport.session()); // persistent login sessions
application.use(flash()); // use connect-flash for flash messages stored in session`
however, if I did it like this, in fact roomPassport overrides passport and instead of having two objects - req.user and req.room, I have got just one req.room but initialized with user data.
It is important to mention that each passports (user or room) could authenticate independently from each other, i.e. there is a scenario where both objects req.user and req.room have to exist.
How to resolve this?
EDIT 1
Well, after few more hours, it seems that although I have to separate passport objects, after the call of application.use(roomPassport.initialize({ userProperty: "room" }));, things get messy - and this is because req.login() method works with the last attached passport. So instead of calling the correct passport.serializeUser method, it calls roomPassport.serializeUser method.
My next question - how to make req.login() to call the right method?
The problem your having is that the passport module exports an instantiated Passport object when you require it. And because that object is cached by node when you require it, you get the exact same object every time.
Luckily, the passport module also gives you a reference to the class, meaning you can do this.
var Passport = require('passport').Passport,
passport = new Passport(),
roomPassport = new Passport();
Now you should have two completely separate passport objects.

req.user after restart node

I've attached passportjs to authenticate user on my site. I use localStrategy and all works fine. But I have some problem (may be it's feature of passportjs) - after restart node, req.user is undefined and users should login again.
What should I do to initialize req.user on start server??
LocalStrategy stores user credentials in memory after login. This means that when you restart node, the credentials will inevitably be lost.
However, passport does use the session cookie functionality in express, so you can persist cookies in the standard express way.
This is quite easy using the RedisStore module, to which you pass the express object:
var express = require('express');
var RedisStore = require('connect-redis')(express);
Before you set up passport, enable session persistence to redis in express:
app.use(express.cookieParser());
app.use(express.session({
secret: "a-secret-token",
store : new RedisStore({
host : 'redis-hostname',
port : 'redis-port',
user : 'redis-username',
pass : 'redis-password'
}),
cookie : {
maxAge : 604800 // one week
}
}));
app.use(passport.initialize());
app.use(passport.session());
You'll need to install redis on your machine, and change the details above to match the redis instance you want to connect to.
You need to store user login info in Redis for e.g. because after restart, all your variables that you set will be undefined

Node Passport session lost on subsequent calls

I believe I have configured everything correctly:
app.configure(function() {
app.use(express.cookieParser('secret message')); // secret it set here in new version of express or connect
app.use(express.bodyParser());
app.use(express.session());
app.use(passport.initialize());
app.use(passport.session());
});
When the request is made to "login" with the correct credentials:
app.post('/api/login', passport.authenticate('local'), function (req, res, next) {
console.log(req.session.passport); // {"user":"5259f2739d4323000a000003"}
});
req.session.passport is populated:
"passport": {"user":"5259f2739d4323000a000003"}
However, when a call is made to:
app.post('/api/checklogin', function (req, res, next) {
console.log(req.session.passport); // {}
})
req.session.passport is lost:
"passport":{}
Both times, req.session looks like this:
{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"passport":{}}
** Passport object is obviously different though, as described above
I assume I have configured serializeUser correctly, because it correctly sets this property.
I am not completely sure how Passport creates session cookies, and how these cookies can persist.
I assume that req.session.passport is supposed to retain the user property, but it seems that the Passport object either:
Resets on every call
Does not actually save the Passport property in the session
The session is never created
I fear that I may be overlooking something large -- possibly something that I may need to do that Passport doesn't handle directly for me.
I do not know of any way to test if the session is created by Passport.
Any advice or help is really appreciated. This has been a multiple day struggle.
Are you using a cluster setup? If so, you need to stop using the default MemoryStore, and switch to something like connect-redis, so different instances of your app can access the shared session data.

Resources