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');
Related
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?
I am using socketio-jwt to authenticate sockets.
On the server:
socketio.use(require('socketio-jwt').authorize({
secret: config.secrets.session,
handshake: true
}));
On the client:
var ioSocket = io('', {
query: 'token=' + Auth.getToken(),
path: '/socket.io-client'
});
My problem is, that if a client is connecting to the server, the authentication fails, and no connection to with the socket is established. Now if the user logs into the system, the connection with the socket is remains not established.
What I try to achieve is, that if a user logs into the system, the connection gets established.
My only idea so far is to reload the page with something like $window.location.href = '/'; after login. But it seems like not the proper way.
Another option would be to keep the socket trying to (re)connect with a timeout. But its a bad option, since my application allows users without login.
How to properly trigger a socket connection?
Here is the solution to your problem
Firstly on your server side you can implement and bind the user credentials to a token as shown below.
var jwt = require('jsonwebtoken');
app.post('/loginpage',function(req,res){
//validate the user here
});
//generate the token of the user based upon the credentials
var server = //create the http server here.
In the socket.io server side you can do the following
//require socket.io
var xxx = socketIo.listen //to the server
xxx.set('auth',socketIojwtauthorize({
secret: config.secrets.session,
handshake: true
}));
xxx.sockets //here connection on or off by setting up promise
//server listen to localhost and port of your choice
When the client sends a valid jwt connection is triggered
Simple client side file would contain the following modules
//function connectsocket(token){
//connect(
//and query the token );
}
//socket.on('connect',function(){
//generate the success message here
}).on('disconnect',function(){ //your choice })
//rendering the routes with the server side
$.post('/loginroute',{
//user credentials passes here
}).done(function(bind){ connect_socket( bind.token )})
var socket = require('socket.io');
var express = require('express');
var http = require('http');
var app = express();
var server = http.createServer(app);
var io = socket.listen(server);
io.on('connection', function (client) {
// here server side code
})
server.listen(5000);
on client side include
node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js
socket = io.connect(site_url + ":5000", { reconnect: !0 });
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];
});
I've followed this chat tutorial http://cestfait.ch/content/chat-webapp-nodejs on creating a simple chat app, Ive been trying to limit this to just 2 users, and I wanted to create 2 variables called user1id, and user2id, I want them to be the socket.id of the client, like
// you set this value when the user1 connects
var user1id;
// you set this value when the user2 connects
var user2id;
// just for visualization, this is one of the questions I have on how to do this.
user1id = socket.id; user2id = socket.id;
the reason I want just 2 users and have them as user1 and user2 is so I can compare their messages, so I need an array of user1's and users 2's messages like
user1arr = []; user2arr = [];
and then just compare for example the "currentWord" variable of user1 and see if that matches anything in user2msgs[]. An example of user1's case below:
function receivedWord(userid, currentWord) {
// this means current player is 1
if( userid == user1id ) {
// adds the word received to user 1 word array
user1arr.push(currentWord);
// compares received word with each word in user 2 array
$.each(user2arr, function(u2word) {
if(u2word == currentWord) {
wordsMatch();
}
}
}
My code is basically that of the chat app I gave at the start of this post.
Thanks.
The problem that you will be having is that every time the user reloads the page, the socketID from socketIO will change. What you will need to do is attach something like a sessionID to the user so you know that even if he reconnects he is the same user. You will need to look at a webframe like expressJS or even connect which are highly flexible middlewares for nodeJS. I've accomplished such a thing with express, so let me get you started with a bit of code for this
//directories
var application_root = __dirname;
//general require
var express = require('express'),
connect = require('connect'),
http = require('http'),
fs = require('fs');
//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
// BE AWARE: this lines have to be written before the router!
app.use(express.cookieParser(secretKey));
app.use(express.session({
store: sessionStore,
secret: secretKey,
key: 'express.sid'
}));
//router
app.use(app.router);
//use static page in public folder
app.use(express.static(path.join(application_root, 'public')));
});
//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');
});
Just read through the code and the comments to understand what they are doing. Basically what you are doing is the create an express server, make the socketIO listen to it, create a sessionStore to make it globally available, lock it with a secretKey and set it to the authorization method of socketIO. What socket then does is making handshakes with the users and then links it to a express session. That means that the express sessionID stays the same throughout the session while socketIO ids switch all the time.
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.