socket.io and session? - node.js

I'm using express framework. I want to reach session data from socket.io. I tried express dynamicHelpers with client.listener.server.dynamicViewHelpers data, but i can't get session data. Is there a simple way to do this? Please see the code
app.listen(3000);
var io = require('socket.io');
var io = io.listen(app);
io.on('connection', function(client){
// I want to use session data here
client.on('message', function(message){
// or here
});
client.on('disconnect', function(){
// or here
});
});

This won't work for sockets going over the flashsocket transport (it doesn't send the server the needed cookies) but it reliably works for everything else. I just disable the flashsocket transport in my code.
To make it work, in the express/connect side, I explicitly define the session store so I can use it inside socket:
MemoryStore = require('connect/middleware/session/memory'),
var session_store = new MemoryStore();
app.configure(function () {
app.use(express.session({ store: session_store }));
});
Then inside my socket code, I include the connect framework so I can use its cookie parsing to retrieve the connect.sid from the cookies. I then look up the session in the session store that has that connect.sid like so:
var connect = require('connect');
io.on('connection', function(socket_client) {
var cookie_string = socket_client.request.headers.cookie;
var parsed_cookies = connect.utils.parseCookie(cookie_string);
var connect_sid = parsed_cookies['connect.sid'];
if (connect_sid) {
session_store.get(connect_sid, function (error, session) {
//HOORAY NOW YOU'VE GOT THE SESSION OBJECT!!!!
});
}
});
You can then use the session as needed.

The Socket.IO-sessions module solution exposes the app to XSS attacks by exposing the session ID at the client (scripting) level.
Check this solution instead (for Socket.IO >= v0.7). See docs here.

I suggest not to entirely reinvent the wheel. The tools you need is already an npm package.I think this is what you need: session.socket.io
I am using it in these days and it will be very helpful I guess!! Linking the express-session to the socket.io layer will have so many advantages!

Edit: After trying some modules that didn't work, I've actually gone and written my own library to do this. Shameless plug: go check it out at https://github.com/aviddiviner/Socket.IO-sessions. I'll leave my old post below for historical purposes:
I got this work quite neatly without having to bypass the flashsocket transport as per pr0zac's solution above. I am also using express with Socket.IO. Here's how.
First, pass the session ID to the view:
app.get('/', function(req,res){
res.render('index.ejs', {
locals: {
connect_sid: req.sessionID
// ...
}
});
});
Then in your view, link it in with Socket.IO client-side:
<script>
var sid = '<%= connect_sid %>';
var socket = new io.Socket();
socket.connect();
</script>
<input type="button" value="Ping" onclick="socket.send({sid:sid, msg:'ping'});"/>
Then in your server-side Socket.IO listener, pick it up and read/write the session data:
var socket = io.listen(app);
socket.on('connection', function(client){
client.on('message', function(message){
session_store.get(message.sid, function(error, session){
session.pings = session.pings + 1 || 1;
client.send("You have made " + session.pings + " pings.");
session_store.set(message.sid, session); // Save the session
});
});
});
In my case, my session_store is Redis, using the redis-connect library.
var RedisStore = require('connect-redis');
var session_store = new RedisStore;
// ...
app.use(express.session({ store: session_store }));
Hope this helps someone who finds this post while searching Google (as I did ;)

See this: Socket.IO Authentication
I would suggest not fetching anything via client.request... or client.listener... as that is not directly attached to the client object and always point to the last logged in user!

For future readers - There is an elegant and easy way to access the session inside socket.io with express-session. From socket.io documentation:
Most existing Express middleware modules should be compatible with Socket.IO, you just need a little wrapper function to make the method signatures match:
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
The middleware functions that end the request-response cycle and do not call next() will not work though.
Example with express-session:
const session = require("express-session");
io.use(wrap(session({ secret: "cats" })));
io.on("connection", (socket) => {
const session = socket.request.session; // here you get access to the session :)
});

Check out Socket.IO-connect
Connect WebSocket Middleware Wrapper Around Socket.IO-node
https://github.com/bnoguchi/Socket.IO-connect
This will allow you to push the Socket.IO request(s) down the Express/Connect middleware stack before handling it with Socket.IO event handlers, giving you access to the session, cookies, and more. Although, I'm not sure that it works with all of Socket.IO's transports.

I am not sure that I am doing it right.
https://github.com/LearnBoost/socket.io/wiki/Authorizing
With the handshake data, you can access to the cookies. And in the cookies, you can grab connect.sid which is the session id for each client. And then use the connect.sid to get the session data from database (I am assuming you are using RedisStore)

You can make use of express-socket.io-session .
Share a cookie-based express-session middleware with socket.io. Works with express > 4.0.0 and socket.io > 1.0.0 and won't be backward compatible.
Worked for me!!

As of February 2022 this is supported by Socket.IO v4:
https://socket.io/docs/v4/faq/#usage-with-express-session
const express = require('express');
const session = require('express-session');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const sessionMiddleware = session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }});
// register middleware in Express
app.use(sessionMiddleware);
// register middleware in Socket.IO
io.use((socket, next) => {
sessionMiddleware(socket.request, {}, next);
// sessionMiddleware(socket.request, socket.request.res, next); will not work with websocket-only
// connections, as 'socket.request.res' will be undefined in that case
});
io.on('connection', (socket) => {
const session = socket.request.session;
session.connections++;
session.save();
});
const port = process.env.PORT || 3000;
server.listen(port, () => console.log('server listening on port ' + port));

You can have a look at this: https://github.com/bmeck/session-web-sockets
or alternatively you can use:
io.on('connection', function(client) {
var session = client.listener.server.viewHelpers;
// use session here
});
Hope this helps.

Related

How to identify which session the websocket connection is from?

I'm working on the game. Each player has a session in which after sign in I keep his name. After sign in, each player connects via the websocket.
How can I tie a player session to a websocket? I would like to make sure that each player session could open only one websocket at the same time, if he opens another one (for example in new tab) I close the previous one.
let express = require('express'),
app = express(),
cookie = require('cookie'),
fs = require('fs'),
server = require('http').Server(app),
webSocket = require('ws'),
session = require('express-session');
let wss = new webSocket.Server({
verifyClient: (info, done) => {
sessionParser(info.req, {}, () => {
// there is no connect.sid when I place application on server, however on local it exists
done(info.req.session);
});
}, server
});
const sessionParser = session({
secret: '$eCuRiTy',
});
app.use(sessionParser);
server.listen(3000);
app.post('/signIn', async function(req, res) {
console.log(req.sessionID); // I want to get the sessionID in websocket
res.send(true);
});
wss.on('connection', async function(ws, req) {
console.log(req.sessionID); // I want to get sessionID from post req.sessionID
});
If I run the application on a local host, everything works fine.
req.sessionID from post is the same like req.sessionID in websocket.
However, if I place the application on the server, the sessions are different.
In headers in info object at verifyClient connect.sid doesnt exist.
How can I fix it?

Does node + socketio + express + https secure the socket data as well

With the following code snippet:
var express = require('express'),
app = express(),
server = require('https').createServer(app),
io = require('socket.io')(server);
app.get('/', function(req, res, next) {
res.sendFile(__dirname + '/assets/html/index.html');
});
/** More routing functions **/
io.on('connection', function(socket) {
components.socket.onConnect(socket, config);
});
io.on('save', function(data){
var saved = save(data);
io.emit('response', saved);
});
/** More Socket.io functions **/
server.listen(443, function() {
console.log("Server Ready.");
});
Assuming this server side setup (with ssl cert) and the clients connect securely, are the data value on save and the saved value emitted back with socket.io also encrypted with the ssl cert like the web data would be too?
The answer is yes as long as you use https for the initial connection. In your example, since you're only ever using https, this will never be a problem for you.

Redis client connects to the redis remote server but can't read or write on it

In DegitalOcean, i have to manage two servers 'Nodeserver' and a redis server 'Redisserver' by making the connection possible from a node/express application on the former for using the latter's database.
For the purpose of local tests, i've also installed another redis server 'LocalRedisOnNodeServer' locally on 'Nodeserver'.
I have configured the 'Redisserver' to accept external connections as in https://www.digitalocean.com/community/tutorials/how-to-set-up-a-redis-server-as-a-session-handler-for-php-on-ubuntu-14-04 and when trying to connect from the 'LocalRedisOnNodeServer's redis-cli i was able to access the remote 'Redis_server' database and reading and writting operations were also possible through it.
I still didn't configure any security's mesures for 'Redis_server' (as to edit iptables..) and i've only set a password (requirepass in the redis.conf) and i still don't know if a more secured authentification can be made with SSH on this server (or to configure my application code for it too).
The following is the app.js file which contains the express's session and the RedisStore (receiving a redis client as args) passed to the session.
var express = require('express');
var app = express();
var routes = require('./routes');
var errorHandlers = require('./middleware/errorhandlers');
var log = require('./middleware/log');
var partials = require('express-partials');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
var bodyParser = require('body-parser');
var redis = require('redis');
var config = require('./config');
app.set('view engine', 'ejs');
app.set('view options', {defaultLayout: 'layout'});
app.use(partials());
app.use(log.logger);
app.use(express.static(__dirname + '/static'));
app.use(cookieParser(config.secret));
console.log(config.redisConf);
var redisClient = redis.createClient(config.redisConf);
redisClient.on('connect', function() {
console.log('connected to redis!!');
});
redisClient.set('framework', 'AngularJS', function(err, reply) {
console.log('the framwork var was SET to AngularJS : the following is the server answer : ');
console.log(reply);
});
app.use(session({
secret: config.secret,
resave: false,
saveUninitialized: true,
store: new RedisStore({client: redisClient})
}));
// right after the session
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.get('/', routes.index);
app.get('/login', routes.login);
app.post('/login', routes.loginProcess);
app.get('/chat', routes.chat);
app.get('/account/login', routes.login);
app.get('/error', function(req, res, next){
next(new Error('A contrived error'));
});
app.use(errorHandlers.error);
app.use(errorHandlers.notFound);
app.listen(config.port);
console.log("App server running on port " + config.port);
and this is the config.js :
var config = {
port: 3000,
secret: 'secret',
redisConf: {
host: 'xxx.xxx.xxx.xxx.', // The redis's server ip
port: '6379',
pass: 'theredispass'
}
};
module.exports = config;
I've tested the app connection to the local redis server 'LocalRedisOnNodeServer' and and it was successful as well as reading and writing keys (for the set and get..).
which is a proof that the app code is fine. But when i changed from the redis host (redisConf.host ) from the local 127.0.0.1 to the 'Redisserver' ip, only the connection occurs (the redisClient.on('connect' callback logs that it's 'connected to redis!!' but reading and writting functions(get and set) fail as their callbacks haven't been triggered and the other problem was that the express's session couldn't be created and its value remains to undefined.
I wonder why the connection as well as reading and writting operations were all possible within the local redis server's client on the 'Node_server's shell (the redis-cli) to the remote server while the redisClient in application code failed.
No errors were mentioned in redis_6379.log
Best regards
I solved it first by adding
client.on("error", function (err) {
console.log("Error " + err);
});
so i could see what was wrong (indeed my mistake was to think that the redisClient belongs to the connect-redis module so i didn't see all these available options on the node_redis module..) and that added code showed me that the problem was like in this thread and thanks to this answer and also, i saw that it was better to move the get methode inside the set's callback as the remote server would answer asynchronuesly..

Scoping of socket.io server side and client side objects in express routes

In my app.js I have
var app = express();
var serv = http.createServer(app);
var io = require('socket.io').listen(serv);
io.sockets.on('connection', function(socket) {
//some code here
}
var SessionSockets = require('session.socket.io'),
sessionSockets = new SessionSockets(io, express_store, cookieParser);
sessionSockets.on('connection', function (err, socket, session) {
//set up some socket handlers here for the specific client that are
//only called when a client does a socket.emit.
//These handlers have access to io, sessionSockets, socket, session objects.
}
How can the express routes access a particular client's socket reference after processing a post/get which is not triggered by a client socket.emit but triggered by a client post/get. What is the best way to scope the socket.io server(io/sessionSockets)/client(socket) objects in routes so that I can get this client's socket reference easily?
These three steps helped me to the solve the problem. This identifies tabs also uniquely as that was one of my requirements.
On connection, join using socket.id and then send the socket.id back to the client using
io.sockets.on('connection', function(socket) {
socket.join(socket.id);
socket.emit('server_socket_id', {socket_id : socket.id});
}
Client receives the emit event using
socket.on('server_socket_id', function(data){
//assign some global here which can be sent back to the server whenever required.
server_socket_id = data.socket_id;
});
In app.js I fetch the corresponding socket like this and pass it on to the routes.
app.post('/update', function(req, res){
var socket_id = req.body.socket_id;
route.update(req, res, io.sockets.in(socket_id).sockets[socket_id]);
});
The best way to do this is to use the socket.io authorization setting, although the module session.socket.io was created specifically for that purpose. Each time a socket establishes a connection, there is handshake data that is stored (although I've heard that flashsockets won't pass the browser cookies). This is what it looks like (and is similarly written in the module you're using):
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
//error object, then boolean that allows/denies the auth
callback(null, true);
});
});
What you could do from here is parse the cookie, then store a reference to that socket by the cookie name. So you would add this to the authorization setting:
var data = handshakeData;
if (data.headers.cookie) {
//note that this is done differently if using signed cookies
data.cookie = parseCookie(data.headers.cookie);
data.sessionID = data.cookie['express.sid'];
}
Then, when you listen on connections, store the client by session identifier:
var clients = {};
io.sockets.on('connection', function(socket) {
//store the reference based on session ID
clients[socket.handshake.sessionID] = socket;
});
And when you receive an HTTP request in Express, you can fetch it like this:
app.get('/', function(req, res) {
//I've currently forgotten how to get session ID from request,
//will go find after returning from school
var socket = clients[sessionID];
});

With socket.io, how to handle authentication with a non-browser client?

I am trying to authenticate a WebSocket connection using socket.io.
My web sockets can receive connections both from a browser, and from a Python or a Node.js client.
In the first case (browser), then the user is authenticated by my Express.js server, and I can just use the session cookie, so no problem.
However, in the second case (random client) I need to handle the authentication myself.
I found from socket.io documentation, that there is a hook for authorizing the connection at the handshake phase. However, there seems to be no way for the client to add custom headers. Looks like the only way to add custom data is in the url query string.
That doesn't seem really secure to me as url is a common thing to be logged, so credentials would be logged as well.
Also, I am wondering if the connection is secure even during the handshake, or if the handshake is not over ssl then it is a major security problem to do it like that.
Well... basically security concerns. Do somebody know what's the right way to do that ? Do I really have to put credentials in the url, and is that secure ?
For such an endeavour you will need to hook the expressID to the sessionID of the socketIO connection. For express 3 that is a whole different story and is usally not that easy. Way back a few months ago I found a way to do so.
//directories
var application_root = __dirname,
//general require
var express = require('express'),
connect = require('connect'),
http = require('http');
//create express app
var app = express();
//create sessionStore
var sessionStore = new connect.session.MemoryStore();
//setup sessionKey
var secretKey = 'superdupersecret';
//configure express
app.configure(function() {
//use body parser
app.use(express.bodyParser());
//override methods
app.use(express.methodOverride());
//cookies and sessions
app.use(express.cookieParser(secretKey));
app.use(express.session({
store: sessionStore,
secret: secretKey,
key: 'express.sid'
}));
//router
app.use(app.router);
});
//create the http server link to the new data http object of express 3
server = http.createServer(app);
//make server listen to 8080 and localhost requests
server.listen(8080, 'localhost', function() {
console.log('Express Server running and listening');
});
//bind socket.io to express server by listening to the http server object
var io = require('socket.io').listen(server);
//set the authorization through cookies
io.set('authorization', function(data, accept) {
//check if header data is given from the user
if (!data.headers.cookie) {
//return false if no cookie was given
return accept('Session cookie required!', false);
}
//parse cookie into object using the connect method
//NOTE: this line is a hack, this is due to the fact
//that the connect.utils.parseCookie has been removed
//from the current connect module and moved to an own
//instance called 'cookie'.
data.cookie = connect.utils.parseSignedCookies(require('cookie').parse(decodeURIComponent(data.headers.cookie)), secretKey);
//save session id based on exress.sid
data.sessionID = data.cookie['express.sid'];
//get associated session for this id
sessionStore.get(data.sessionID, function(err, session) {
if (err) {
//there was an error
return accept('Error in session store.', false)
} else if (!session) {
//session has not been found
return accept('Session not found', false);
}
//successfully got the session, authed with known session
data.session = session;
//return the accepted connection
return accept(null, true);
});
});
//connection handler for new io socket connections
io.sockets.on('connection', function(socket) {
//parse handshake from socket to io
var hs = socket.handshake;
//show session information
console.log('New socket connection with sessionID ' + hs.sessionID + ' connected');
});
//handle user disconnect and clear interval
io.sockets.on('disconnect', function() {
//show disconnect from session
console.log('Socket connection with sessionID ' + hs.sessionID + ' disconnected');
});
So it looks like the only solution is to add your authentication data in the url query string. There's an open ticket for allowing other possibilities, but it looks like the developers are leaning towards using the query strings only.
This works for me: https://gist.github.com/jfromaniello/4087861. I had to change the socket.io-client package.json to use xmlhttprequest version 1.5.0 which allows setting the cookie header. I also had to change both requires to use the socket.io xmlhttprequest:
require('socket.io-client/node_modules/xmlhttprequest')
instead of
require ('xmlhttprequest');

Resources