¿Cómo compartir sesiones con Socket.IO 1.xy Express 4.x?

89

¿Cómo puedo compartir una sesión con Socket.io 1.0 y Express 4.x? Utilizo Redis Store, pero creo que no debería importar. Sé que tengo que usar un middleware para ver las cookies y recuperar la sesión, pero no sé cómo. Busqué pero no pude encontrar ninguno que funcionara

    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'));
        }
    });
Mustafa
fuente

Respuestas:

215

La solución es sorprendentemente sencilla. Simplemente no está muy bien documentado. También es posible utilizar el middleware de sesión rápida como middleware Socket.IO con un pequeño adaptador como este:

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

Aquí hay un ejemplo completo con express 4.x, Socket.IO 1.xy 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);
Epeli
fuente
17
¿Podrías ayudarme con tu solución? Solo obtengo estos datos {cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true, secure: true } }Pero si imprimo la sesión en mis rutas, obtengo todas las variables de sesión que configuré (nombre de usuario, identificación, etc.)
Bobby Shark
7
Esto debería agregarse totalmente a sus documentos. La documentación de autenticación es súper liviana como lo es actualmente.
Bret
4
esto "funciona" para mí, pero mi ID de sesión Express no es el mismo que mi ID de sesión socket.io ... ¿tal vez no quiero que sean iguales de todos modos?
Alexander Mills
4
¡Esta solución funcionó muy bien! ... hasta que necesité guardar datos en la sesión desde dentro de un socket.on () en ese punto, descubrí que es solo una forma. ¿Hay alguna forma de hacer que funcione en ambos sentidos?
iDVB
4
Esto funcionó muy bien con un par de modificaciones. Pero no pude escribir en la sesión desde socket.io. Encontré un paquete de NPM que cumplió con todas mis necesidades y su implementación requiere el mismo esfuerzo que esta respuesta. npmjs.com/package/express-socket.io-session
Bacon Brad
6

Hace solo un mes y medio me enfrenté al mismo problema y luego escribí una extensa publicación de blog sobre este tema que va junto con una aplicación de demostración completamente funcional alojada en GitHub. La solución se basa en módulos de nodo express-session , cookie-parser y connect-redis para atar todo. Le permite acceder y modificar sesiones desde el contexto REST y Sockets, lo cual es bastante útil.

Las dos partes cruciales son la configuración del middleware:

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

... y configuración del servidor SocketIO:

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

Van junto con un módulo sessionService simple que hice que le permite hacer algunas operaciones básicas con sesiones y ese código se ve así:

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

Dado que hay más código para todo esto (como inicializar módulos, trabajar con sockets y llamadas REST tanto en el lado del cliente como en el del servidor), no pegaré todo el código aquí, puedes verlo en GitHub. y puedes hacer lo que quieras con él.

pootzko
fuente
4

express-socket.io-session

es una solución lista para su problema. Normalmente, la sesión creada en el extremo de socket.io tiene un sid diferente a los creados en express.js

Antes de conocer ese hecho, cuando estaba trabajando en ello para encontrar la solución, encontré algo un poco extraño. Las sesiones creadas a partir de la instancia de express.js eran accesibles en el extremo de socket.io, pero no era posible lo mismo para lo contrario. Y pronto me di cuenta de que tengo que trabajar a través de la gestión de Sid para resolver ese problema. Pero, ya había un paquete escrito para abordar ese problema. Está bien documentado y hace el trabajo. Espero eso ayude

Rahil051
fuente
2

Usando la respuesta de Bradley Lederholz, así es como lo hice funcionar para mí. Consulte la respuesta de Bradley Lederholz para obtener más explicaciones.

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
fuente
Trabajó para mi. Encontré a mi usuario en socket.request.user
Milazi
0

Lo he resuelto un poco, pero no es perfecto. No admite cookies firmadas, etc. Utilicé la función getcookie de express-session . La función modificada es la siguiente:

    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
fuente
0

Ahora, la respuesta original aceptada tampoco me funciona. Al igual que @ Rahil051, utilicé el módulo express-socket.io-session y todavía funciona. Este módulo utiliza un analizador de cookies para analizar la identificación de la sesión antes de ingresar al middleware de sesión rápida. Creo que es silmiar para @pootzko, @Mustafa y la respuesta de @ Kosar.

Estoy usando estos módulos:

"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"
}

echa un vistazo a los datos en 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);
    });
};
Pentatónico
fuente