95

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'));
        }
    });
Epeli
  • 16,564
  • 10
  • 63
  • 76
Mustafa
  • 9,075
  • 7
  • 62
  • 109

7 Answers7

223

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);
GLAND_PROPRE
  • 3,318
  • 2
  • 21
  • 47
Epeli
  • 16,564
  • 10
  • 63
  • 76
  • 18
    Could you help me with your solution ? I only get this data { cookie: `{ path: '/', _expires: null, originalMaxAge: null, httpOnly: true, secure: true } }` But if I print the session in my routes I get all the session variables I've set up (username, id, etc..) – Bobby Shark Oct 31 '14 at 13:07
  • 2
    In my case, socket.request.res is undefined. do you know what the problem is? – Anderson Nov 28 '14 at 02:26
  • @Anderson Old version? – Epeli Nov 28 '14 at 08:41
  • 7
    This should totally get added to their docs. Authentication documentation is super light as they are currently. – Bret Feb 11 '15 at 15:57
  • @Anderson I have it undefined too, all modules are newest versions. What I do? – youbetternot Feb 28 '15 at 01:29
  • @Epeli I have it undefined too, all modules are newest versions. What I do? – youbetternot Feb 28 '15 at 01:29
  • Hah, spent a lot of time trying to debug the same symptoms as Bobby Shark. Finally realized it's working fine, the only "problem" was that there was nothing else stored in my session :P – luff Mar 04 '15 at 11:48
  • It really helped me some time ago but now i figured out that i can't modify session object from sockets, only from REST. I can modify session once from sockets by calling save() manually on session object (as im using mongostore) but then it seem this brokes something and furhter changes of those value sometimes not working – Max Yari Mar 28 '15 at 20:33
  • retested again, it seems that i can call save on session once in socket without screwing everything, if ill make it twice -accessing session from REST stops working – Max Yari Mar 28 '15 at 20:51
  • 1
    does this work with native mobile socket.io clients? as far as I've read "headers" are not even supposed to be sent via websocket connections. See: https://github.com/Automattic/socket.io-client/issues/648#issuecomment-42154344 – Hayk Saakian May 27 '15 at 03:22
  • 4
    this "works" for me, but my Express session ID is not the same as my socket.io session ID...maybe I don't really want them to be the same anyway? – Alexander Mills Jun 23 '15 at 22:45
  • 3
    What can be reason for socket.request.res to be undefined? – Srle Sep 11 '15 at 14:34
  • 4
    This solution worked GREAT! ...until I needed to save data to the session from within a socket.on() at that point I'm finding that its only one way. Is there a way to make it work both ways? – iDVB Sep 11 '15 at 19:01
  • 1
    Works but the session doesn't update. I have to manually disconnect then reconnect the socket when the user logs out and back in to load their new credentials. – Tamlyn Sep 23 '15 at 11:48
  • 3
    I had the simmilar problem as @youbetternot and find workaround to this issue with changing to: `sessionMiddleware(socket.request, {}, next);` – Artem Baranovskii Nov 20 '15 at 13:46
  • 2
    same problem as @AlexMills, express session id and socket session id seems to be different... here's a [different question](http://stackoverflow.com/questions/33913758/express-session-isnt-setting-session-cookie-while-using-with-socket-io) with whole code – T J Nov 25 '15 at 13:00
  • I was able to use this and it worked when my socket.io server was running on the same machine as my express server. I have them on separate machines and I have both machines connecting to the same redis instance, but when the socket requests get sent to the socket.io server it just creates a new session. A link to anything relevant would be awesome!! – user2278120 Dec 09 '15 at 23:36
  • What the... there are plenty of wrapper libraries that can be used to enable session and you did without no shit... Awesome..... – binariedMe Dec 10 '15 at 10:59
  • 2
    Sorry to revive this thread, however I'm having the same issue as @BobbyShark and @luff. Luff said that he was able to figure it out however didn't say how. I am storing session variables using `req.session.propName = value`. These don't appear in `socket.request.session`. – Levi Roberts Feb 26 '16 at 21:15
  • Upon further inspection; The socket.io and Express session ID's are different - could this potentially be why? – Levi Roberts Feb 26 '16 at 21:23
  • @Epeli I was able to use this and it use to work, but somehow it doesn't work anymore, I posted question about my issue here: http://stackoverflow.com/questions/36983211/issue-with-sharing-session-between-express-and-socket-io – EspeH May 02 '16 at 13:19
  • 1
    For those who have a different ID for socket and HTTP request sessions, you must use the same middleware for both (do not use two different `var sessionMiddleware = session({...});`). This problem can also be caused by [Node.js clusters](http://socket.io/docs/using-multiple-nodes/#using-node.js-cluster). – Gnucki May 04 '16 at 13:10
  • 1
    @Gnucki I still have different IDs, even though I use the same variable for both app.use() and io.use(). I wonder if this does not cause some issues. Do you have any new information about this ? – Arpp May 14 '16 at 21:21
  • Sorry but I cannot help you further without any code. – Gnucki May 15 '16 at 14:51
  • 1
    @Anderson, youbetternot, Srle. I had similar problem with undefined. Problem was in redis store. Everything became working well once I changed store to default memory store or file store. – Denis May 25 '16 at 22:59
  • @iDVB were you every able to find a solution for modifying the session from a socket.io listener? – tbenst Jun 08 '16 at 10:01
  • Whatever I try, I still get undefined for both objects, and can not set them so I can read one from the other. – Király István Jul 28 '16 at 22:56
  • @BobbyShark I got the some problem of yours but then I found out that I didn't include params with redis `new RedisStore({host:'localhost',port:6379})` – abderrahmen Sep 03 '16 at 12:43
  • 4
    This worked great with a couple modifications. But I could not write to the session from socket.io. I found an NPM package that met all my needs and takes about the same effort as this answer to implement. https://www.npmjs.com/package/express-socket.io-session – Bacon Brad Sep 13 '16 at 21:16
  • Does this mean literally including "socket.request.session" in `io.on('connection')` and "req.session" on `app.use(...` as he has above are required to make the data available? – Himmel Feb 15 '17 at 23:18
  • One note: in my case, I also had to pass the option `cookie: false` to the Socket.io server constructor – Turner Hayes Jul 03 '17 at 18:54
  • What excatly is the secret here? `secret: "keyboard cat",` and how excatly can I run this function sorry not much experienced in node – utdev Nov 22 '17 at 14:37
  • 1
    This example was added in the FAQ section here: https://socket.io/docs/faq/#Usage-with-express-session – darrachequesne Apr 15 '20 at 09:24
6

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.

tkit
  • 6,257
  • 6
  • 36
  • 61
5

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

Rahil051
  • 133
  • 1
  • 6
2

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);
Ali
  • 378
  • 3
  • 8
1

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,
});
GARAMANI
  • 11
  • 1
0

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);
Mustafa
  • 9,075
  • 7
  • 62
  • 109
0

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);
    });
};