socket.io and express 4 sessions

I would like to access the express 4 session in my socket.io app. I'm kind of new with Node and I have some troubles implementing this functionality.

I found a npm module that allows access to the express 4 session : https://www.npmjs.org/package/session.socket.io-express4 or https://github.com/eiriklv/session.socket.io

If you look at my app.js code below, I'm doing something wrong in the session, sessionStore or cookieParser setup because I just can't get this module working.

// init modules
var express = require('express');
var helmet = require('helmet');
var fs = require('fs');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var memoryStore = session.MemoryStore;
var app = express();

// set variables
var options = {
  key: fs.readFileSync('./openssl_keys/server_key.pem'),
  cert: fs.readFileSync('./openssl_keys/server_cert.pem')
};
var cookieSecret = "secret phrase";
var sessionStore = new memoryStore();

app.set('env', process.env.NODE_ENV || 'development');

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser(cookieSecret));
app.use(session({
    secret: cookieSecret,
    cookie: {httpOnly: true, secure: true},
    store: sessionStore
}));
app.use(function(req, res, next){
    res.locals.session = req.session;
    next();

});
app.use(express.static(path.join(__dirname, 'public')));

//routes
require('./routes/index')(app);
require('./routes/test')(app);


// starting http and https servers
var http = require('http').createServer(app).listen(8000, function(){
    console.log("http server listening on port 8000");
});
var https = require('https').createServer(options, app).listen(8080, function(){
    console.log("https server listening on port 8080"); 
});

// starting socket.io & session handler
var serverIO = require('socket.io').listen(https);
var SessionSockets  = require('session.socket.io-express4');
var io = new SessionSockets(serverIO, sessionStore, cookieParser);

io.on('connection', function(err, socket, session){
    if(err) throw err;
    console.log("connected");
    //console.log(session);
    socket.on('clientMessage', function(content) {
        console.log("received client message")
        console.log(content);
    });

});

module.exports = app;

I tried multiples possibilities like :

  • Disabling https server.
  • Setting up a cookieParser object with secret phrase (so it "actually" exports the secret phrase to io = new SessionSockets(serverIO, sessionStore, cookieParser);)
  • Using minimal cookie options.

Anyway I'm a bit lost with this, any suggestions/critics are welcome.


UPDATE

Ok so after numerous tries I think I could get it work!

The problem is with the cookieParser initialization which the correct way seems to be :

var cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(session({
    secret: "secret phrase",
    cookie: {httpOnly: true, secure: true},
    store: sessionStore
}));
var io = new SessionSockets(serverIO, sessionStore, cookieParser());

Notice that if I use var io = new SessionSockets(serverIO, sessionStore, cookieParser); (instead of cookieParser()) then it ain't working. That seems to be the problem.

If I use :

app.use(cookieParser("secret phrase"));
app.use(session({
    secret: "secret phrase",
    cookie: {httpOnly: true, secure: true},
    store: sessionStore
}));
var io = new SessionSockets(serverIO, sessionStore, cookieParser("secret phrase"));

then the module crashes with the following error message :

session.socket.io-express4/session.socket.io.js:41
ake.signedCookies[key] = handshake.signedCookies[key].match(/\:(.*)\./).pop();
                                                                        ^
TypeError: Cannot call method 'pop' of null

But if I use :

app.use(cookieParser("secret phrase"));
app.use(session({
    secret: "secret phrase",
    cookie: {httpOnly: true, secure: true},
    store: sessionStore
}));
var io = new SessionSockets(serverIO, sessionStore, cookieParser());

Then everything looks fine.

Now in the cookie-parser doc (https://github.com/expressjs/cookie-parser) it's saying you can pass a secret key to get the cookies signed. Which is something I'd like to have.

Could someone explain me the relation with the cookie-parser secret phrase and the session secret phrase ? Do they have to be the same/different ?


Solution 1:

Here's my solution for the following environment:

  • express 4.2.0
  • socket.io 1.1.0
  • cookie-parser 1.0.1
  • cookie-session 1.0.2

Code:

var cookieParser = require('cookie-parser')();
var session = require('cookie-session')({ secret: 'secret' };

...

app.use(cookieParser);
app.use(session);

...

io.use(function(socket, next) {
    var req = socket.handshake;
    var res = {};
    cookieParser(req, res, function(err) {
        if (err) return next(err);
        session(req, res, next);
    });
});

Then you can access the session from the socket's handshake:

io.on('connection', function (socket) {
    console.log("Session: ", socket.handshake.session);
});

For people wondering how/why this works:

  • We send the handshake request through the cookie parser so that cookies are available
  • Then we send the handshake through session middleware as if its a normal request
  • The middleware attaches session to the request
  • We use handshake because for all intents and purposes, it is a normal request, and the parser and session middleware can deal with it properly. This is why you must access the session through the handshake

Solution 2:

With the new express-session middleware all you have to do is to add the IO middleware:

io.use(function(socket, next) {
  session(socket.handshake, {}, next);
});

A complete example would look like this:

var io = require('socket.io')(server);

var Session = require('express-session'),
    SessionStore = require('session-file-store')(Session);
    session = Session({
      store: new SessionStore({ path: './tmp/sessions' }),
      secret: 'pass',
      resave: true,
      saveUninitialized: true
    });

io.use(function(socket, next) {
  session(socket.handshake, {}, next);
});

io.on('connection', function(socket){
  console.log('a user connected');
  socket.emit('chat message', "UserID: " + socket.handshake.session.uid);
});

I created a super mini npm package socket.io-express-session which works as I explained above.

Solution 3:

This worked for me with

  • express 4.9.0
  • express.io 1.1.13
  • connect-redis 2.1.0
  • express-session 1.8.2

What I wanted was to share sessions with a frontend and backend API through redis. Separate machines, sharing same DB. Sessions are created and users logged in when they open page on the frontend, then the api looks up the logged in users on requests.

var cookieParser = require('cookie-parser')();
var session = require('express-session');
var RedisStore = require('connect-redis')(session);

var db = require('./db')(config);

var sessionStore = session( {
        store: new RedisStore({ client: db }),
        secret: SECRET,
        cookie: { secure: false }
    }
);

app.use(cookieParser);
app.use(sessionStore);

// attach sessions to pure websocket requests
app.io.use(function(req, next) {
    var res = {};
    cookieParser(req, res, function(err) {
        if (err) { return next(err); }
        sessionStore(req, res, next);
    });
});

Note: I set the cookie.secure to false so I can test without https locally.

Solution 4:

express 4.13.4 / socket.io 1.4.5

I browse all solutions and modules, but them all not working in my app. Finaly -

app.use(session({

      secret: COOKIE_SECRET,
      resave: true,
      saveUninitialized: true,
      store:sessionStore,
      cookie: { domain: 'localhost',secure: false } 

}));  


io.use(function(socket, next) {
                          session({
                              secret: COOKIE_SECRET,
                              resave: true,
                              saveUninitialized: true,
                              store:sessionStore,
                              cookie: { domain: 'localhost',secure: false } 
                          })(socket.handshake, {}, next);
});

working like a charm.

Solution 5:

This may work express 4 / socket.io 1.X I grabbed this code form https://github.com/turbonetix/bus.io/blob/master/demo/chat/app.js

io.use(function (socket, next) {
 var handshake = socket.handshake;
  if (handshake.headers.cookie) {
  cookieParser()(handshake, {}, function (err) {
  handshake.sessionID = connect.utils.parseSignedCookie(handshake.cookies[config.session.key], config.session.secret);
    handshake.sessionStore = config.session.store;
    handshake.sessionStore.get(handshake.sessionID, function (err, data) {
      if (err) return next(err);
      if (!data) return next(new Error('Invalid Session'));
      handshake.session = new session.Session(handshake, data);
      next();
    });
   });
 }
 else {
  next(new Error('Missing Cookies'));
 }
});