How to share sessions with Socket.IO 1.x and Express 4.x?

node.jsSessionExpresssocket.io

node.js Problem Overview


How can I share a session with Socket.io 1.0 and Express 4.x? I use a Redis Store, but I believe it should not matter. I know I have to use a middleware to look at cookies and fetch session, but don't know how. I searched but could not find any working

	var RedisStore = connectRedis(expressSession);
	var session = expressSession({
		store: new RedisStore({
			client: redisClient
		}),
		secret: mysecret,
		saveUninitialized: true,
		resave: true
	});
	app.use(session);

	io.use(function(socket, next) {
		var handshake = socket.handshake;
		if (handshake.headers.cookie) {
			var str = handshake.headers.cookie;
			next();
		} else {
			next(new Error('Missing Cookies'));
		}
	});

node.js Solutions


Solution 1 - node.js

The solution is surprisingly simple. It's just not very well documented. It is possible to use the express session middleware as a Socket.IO middleware too with a small adapter like this:

sio.use(function(socket, next) {
    sessionMiddleware(socket.request, socket.request.res, next);
});

Here's a full example with express 4.x, Socket.IO 1.x and Redis:

var express = require("express");
var Server = require("http").Server;
var session = require("express-session");
var RedisStore = require("connect-redis")(session);

var app = express();
var server = Server(app);
var sio = require("socket.io")(server);

var sessionMiddleware = session({
    store: new RedisStore({}), // XXX redis server config
    secret: "keyboard cat",
});

sio.use(function(socket, next) {
    sessionMiddleware(socket.request, socket.request.res || {}, next);
});

app.use(sessionMiddleware);

app.get("/", function(req, res){
    req.session // Session object in a normal request
});

sio.sockets.on("connection", function(socket) {
  socket.request.session // Now it's available from Socket.IO sockets too! Win!
});


server.listen(8080);

Solution 2 - node.js

Just a month and a half ago I dealt with the same problem and afterwards wrote an extensive blog post on this topic which goes together with a fully working demo app hosted on GitHub. The solution relies upon express-session, cookie-parser and connect-redis node modules to tie everything up. It allows you to access and modify sessions from both the REST and Sockets context which is quite useful.

The two crucial parts are middleware setup:

app.use(cookieParser(config.sessionSecret));
app.use(session({
    store: redisStore,
    key: config.sessionCookieKey,
    secret: config.sessionSecret,
    resave: true,
    saveUninitialized: true
}));

...and SocketIO server setup:

ioServer.use(function (socket, next) {
    var parseCookie = cookieParser(config.sessionSecret);
    var handshake = socket.request;
 
    parseCookie(handshake, null, function (err, data) {
        sessionService.get(handshake, function (err, session) {
            if (err)
                next(new Error(err.message));
            if (!session)
                next(new Error("Not authorized"));
 
            handshake.session = session;
            next();
        });
    });
});

They go together with a simple sessionService module I made which allows you to do some basic operations with sessions and that code looks like this:

var config = require('../config');
 
var redisClient = null;
var redisStore = null;
 
var self = module.exports = {
    initializeRedis: function (client, store) {
        redisClient = client;
        redisStore = store;
    },
    getSessionId: function (handshake) {
        return handshake.signedCookies[config.sessionCookieKey];
    },
    get: function (handshake, callback) {
        var sessionId = self.getSessionId(handshake);
 
        self.getSessionBySessionID(sessionId, function (err, session) {
            if (err) callback(err);
            if (callback != undefined)
                callback(null, session);
        });
    },
    getSessionBySessionID: function (sessionId, callback) {
        redisStore.load(sessionId, function (err, session) {
            if (err) callback(err);
            if (callback != undefined)
                callback(null, session);
        });
    },
    getUserName: function (handshake, callback) {
        self.get(handshake, function (err, session) {
            if (err) callback(err);
            if (session)
                callback(null, session.userName);
            else
                callback(null);
        });
    },
    updateSession: function (session, callback) {
        try {
            session.reload(function () {
                session.touch().save();
                callback(null, session);
            });
        }
        catch (err) {
            callback(err);
        }
    },
    setSessionProperty: function (session, propertyName, propertyValue, callback) {
        session[propertyName] = propertyValue;
        self.updateSession(session, callback);
    }
};

Since there is more code to the whole thing than this (like initializing modules, working with sockets and REST calls on both the client and the server side), I won't be pasting all the code here, you can view it on the GitHub and you can do whatever you want with it.

Solution 3 - node.js

express-socket.io-session

is a ready-made solution for your problem. Normally the session created at socket.io end has different sid than the ones created in express.js

Before knowing that fact, when I was working through it to find the solution, I found something a bit weird. The sessions created from express.js instance were accessible at the socket.io end, but the same was not possible for the opposite. And soon I came to know that I have to work my way through managing sid to resolve that problem. But, there was already a package written to tackle such issue. It's well documented and gets the job done. Hope it helps

Solution 4 - node.js

Using Bradley Lederholz's answer, this is how I made it work for myself. Please refer to Bradley Lederholz's answer, for more explanation.

var app = express();
var server  = require('http').createServer(app);
var io = require('socket.io');
var cookieParse = require('cookie-parser')();
var passport = require('passport');
var passportInit = passport.initialize();
var passportSession = passport.session();
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var sessionMiddleware = session({
  secret: 'some secret',
  key: 'express.sid',
  resave: true,
  httpOnly: true,
  secure: true,
  ephemeral: true,
  saveUninitialized: true,
  cookie: {},
  store:new mongoStore({
  mongooseConnection: mongoose.connection,
  db: 'mydb'
  });
});

app.use(sessionMiddleware);
io = io(server);
io.use(function(socket, next){
  socket.client.request.originalUrl = socket.client.request.url;
  cookieParse(socket.client.request, socket.client.request.res, next);
});

io.use(function(socket, next){
  socket.client.request.originalUrl = socket.client.request.url;
  sessionMiddleware(socket.client.request,   socket.client.request.res, next);
});

io.use(function(socket, next){
  passportInit(socket.client.request, socket.client.request.res, next);
});

io.use(function(socket, next){
  passportSession(socket.client.request, socket.client.request.res, next);
});

io.on('connection', function(socket){
  ...
});

... 
server.listen(8000);

Solution 5 - node.js

Working Example for PostgreSQL & Solving the problem of getting "an object with empty session info and only cookies":

Server-Side (Node.js + PostgreSQL):

const express = require("express");
const Server = require("http").Server;
const session = require("express-session");
const pg = require('pg');
const expressSession = require('express-session');
const pgSession = require('connect-pg-simple')(expressSession);
  
const PORT = process.env.PORT || 5000;

const pgPool = new pg.Pool({
    user : 'user',
    password : 'pass',
    database : 'DB',
    host : '127.0.0.1',
    connectionTimeoutMillis : 5000,
    idleTimeoutMillis : 30000
});

const app = express();

var ioServer = require('http').createServer(app);
var io = require('socket.io')(ioServer);

var sessionMiddleware = session({
    store: new RedisStore({}), // XXX redis server config
    secret: "keyboard cat",
});

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

app.use(session);

io.on("connection", socket => {
  const ioSession = socket.request.session;
  socket.on('userJoined', (data) => {
    console.log('---ioSession---', ioSession)
  }
}

Client-Side (react-native app): To solve the problem of getting "empty session object" you need to add withCredentials: true

this.socket = io(`http://${ip}:5000`, {
  withCredentials: true,
});

Solution 6 - node.js

I have kinda solved it, but it is not perfect. Does not support signed cookies etc. I used express-session 's getcookie function. The modified function is as follows:

	io.use(function(socket, next) {
		var cookie = require("cookie");
		var signature = require('cookie-signature');
		var debug = function() {};
		var deprecate = function() {};

		function getcookie(req, name, secret) {
			var header = req.headers.cookie;
			var raw;
			var val;

			// read from cookie header
			if (header) {
				var cookies = cookie.parse(header);

				raw = cookies[name];

				if (raw) {
					if (raw.substr(0, 2) === 's:') {
						val = signature.unsign(raw.slice(2), secret);

						if (val === false) {
							debug('cookie signature invalid');
							val = undefined;
						}
					} else {
						debug('cookie unsigned')
					}
				}
			}

			// back-compat read from cookieParser() signedCookies data
			if (!val && req.signedCookies) {
				val = req.signedCookies[name];

				if (val) {
					deprecate('cookie should be available in req.headers.cookie');
				}
			}

			// back-compat read from cookieParser() cookies data
			if (!val && req.cookies) {
				raw = req.cookies[name];

				if (raw) {
					if (raw.substr(0, 2) === 's:') {
						val = signature.unsign(raw.slice(2), secret);

						if (val) {
							deprecate('cookie should be available in req.headers.cookie');
						}

						if (val === false) {
							debug('cookie signature invalid');
							val = undefined;
						}
					} else {
						debug('cookie unsigned')
					}
				}
			}

			return val;
		}

		var handshake = socket.handshake;
		if (handshake.headers.cookie) {
			var req = {};
			req.headers = {};
			req.headers.cookie = handshake.headers.cookie;
			var sessionId = getcookie(req, "connect.sid", mysecret);
			console.log(sessionId);
			myStore.get(sessionId, function(err, sess) {
				console.log(err);
				console.log(sess);
				if (!sess) {
					next(new Error("No session"));
				} else {
					console.log(sess);
					socket.session = sess;
					next();
				}
			});
		} else {
			next(new Error("Not even a cookie found"));
		}
	});

	// Session backend config
	var RedisStore = connectRedis(expressSession);
	var myStore = new RedisStore({
		client: redisClient
	});
	var session = expressSession({
		store: myStore,
		secret: mysecret,
		saveUninitialized: true,
		resave: true
	});
	app.use(session);

Solution 7 - node.js

Now, the original accepted answer doesn't work for me either. Same as @Rahil051, I used express-socket.io-session module, and it still works. This module uses cookie-parser, to parse session id before entering express-session middleware. I think it's silmiar to @pootzko, @Mustafa and @Kosar's answer.

I'm using these modules:

"dependencies": 
{
  "debug": "^2.6.1",
  "express": "^4.14.1",
  "express-session": "^1.15.1",
  "express-socket.io-session": "^1.3.2
  "socket.io": "^1.7.3"
}

check out the data in socket.handshake:

const debug = require('debug')('ws');
const sharedsession = require('express-socket.io-session');

module.exports = (server, session) => {
    const io = require('socket.io').listen(server);
    let connections = [];

    io.use(sharedsession(session, {
        autoSave: true,
    }));

    io.use(function (socket, next) {
        debug('check handshake %s', JSON.stringify(socket.handshake, null, 2));
        debug('check headers %s', JSON.stringify(socket.request.headers));
        debug('check socket.id %s', JSON.stringify(socket.id));
        next();
    });

    io.sockets.on('connection', (socket) => {
        connections.push(socket);
    });
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMustafaView Question on Stackoverflow
Solution 1 - node.jsEpeliView Answer on Stackoverflow
Solution 2 - node.jstkitView Answer on Stackoverflow
Solution 3 - node.jsRahil051View Answer on Stackoverflow
Solution 4 - node.jsAliView Answer on Stackoverflow
Solution 5 - node.jsGARAMANIView Answer on Stackoverflow
Solution 6 - node.jsMustafaView Answer on Stackoverflow
Solution 7 - node.jsPentatonicView Answer on Stackoverflow