How to identify which session the websocket connection is from? - node.js

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?

Related

How to prevent people from connecting to any id with sockeIO?

I just set up SocketIO in my PHP project. I am completly new to websockets at all so bear with me.
I am defining the socketIO variable globally
let socketIO = io("http://localhost:3000");
When people are logging in to my application, they are connected to it with their ID comming from the database. The login script just gives back true which redirects the user in very simplified terms:
// get component
$.get(url, data, (data) => {
if (data.status) {
// connect with Node JS server
socketIO.emit("connected", data.user_id);
// redirect
load_new_page("/users/" + data.user_id);
}
});
My concern here now is that people could just go and change the data.user_id to anything they want and receive what ever the chosen id would receive.
My server.js:
// initialize express server
var express = require("express");
var app = express();
// create http server from express instance
var http = require("http").createServer(app);
// include socket IO
var socketIO = require("socket.io")(http, {
cors: {
origin: ["http://localhost"],
},
});
// start the HTTP server at port 3000
http.listen(process.env.PORT || 3000, function () {
console.log("Server started running...");
// an array to save all connected users IDs
var users = [];
// called when the io() is called from client
socketIO.on("connection", function (socket) {
// called manually from client to connect the user with server
socket.on("connected", function (id) {
users[id] = socket.id;
});
});
});
How can I prevent something like this?

WebSocket in Node routes

I have a REST API backend. I'm trying to send WebSocket messages back to the client app when a site administrator invokes a route (i.e. updates user credentials);
let express = require("express");
let router = express.Router();
//update user credentials
router.put('/admin/user/:id', (req, res)=>{
// 1. Admin updates target user's profile/credentials
// 2. WebSocket message sent to target user client
// 3. Route sends res.status(200) to admin
});
I've seen a few WebSocket examples using 'ws', 'net', 'websocket' libraries, but they all show a simple event handling socket server that responds to socket messages outside of any express routes - let alone responds to a separate client.
Also, the event notification should be visible only to the target user and not all the other users connected to the socket server.
Figured it out. The WebSocket server is independent of the route.
const WebSocket = require("ws");
const wss = new WebSocket.Server({port: 5555});
// handle socket communications
wss.on('connection', (session)=> {
session.send("HELLO SESSION: "+ session.userid);
session.on('message', (message)=> {
console.log(`MSG: "${message}" recived.`);
session.send("Got it.");
});
});
// close
wss.on('close', function close() {
clearInterval(interval);
});
module.exports = wss;
Then, in the route, just include the service and use it to iterate through web socket connections like so:
let wss = require('./mysocketservice')
...
function sendAMessageTo(user, msg) {
wss.clients.forEach(function each(s) {
if(session.user = user)
session.send("still there?.. ");
}
}

How to set 2 users socket.id into 2 variables, user1id and user2id in socket.io/node.js

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.

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

socket.io and session?

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.

Resources