Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Socket.io - Implementing a user-socket association map for private messaging

I'm trying to create a private messaging system using socket.io

In order to associate the users with their sockets, most sites are suggesting something like this:

var people = {};

client.on('connection', function(socket) {

    //join the server
    socket.on('add user', function(user_id) {
        //create user-socket map
        people[user_id] = socket.id;
    });

});

In my opinion, the problem with the above code is that the user_id is sent from the client side, so if the user somehow modify it and send another user_id an impersonation will take place.

Also, I can't access req.user._id under client.on('connection'... so how am I supposed to get the user_id from the server's side? I'm using node.js, passport and express.

like image 477
Light Flow Avatar asked Nov 24 '15 13:11

Light Flow


Video Answer


3 Answers

I would use jsonwebtoken and socketio-jwt modules for solving this security issue.

Server:

var secret = 'shhhhhh';
app.get('/getJWT', function(req, res) {
    /* Do your authentication here using header and get the userId */
    var userId = 'someId';
    var jwt = require('jsonwebtoken');
    var token = jwt.sign({ 'userId': userId }, secret);
    res.json({
        token: token
    });
});

var socketioJwt = require('socketio-jwt');
io.use(socketioJwt.authorize({
    secret: secret,
    handshake: true
}));

io.sockets.on('connection', function (socket) {
    var userId = socket.decoded_token.userId;
    /* your logic */

Client:

var token = "token you got from the /getJWT"
var c = io.connect('http://localhost:3000/', { query: "token=" + token });

As the token is encoded with a secret, client cannot change and send it.

Refer this article to know why this is better.

like image 140
Anand S Avatar answered Sep 27 '22 21:09

Anand S


var people will be accessible on the same process.

When you want to scale with multiple socket server and balancing between them, then this idea for keeping people object locally will be not helpful.

Create authenticate event for authentication and set socket.userId and socket.isAuthenticate = true flag. In other events if socket.isAuthenticate is false, kick them out.

Make use of 'socket.io-redis' adpater for communication among many socket.io server. ( So when user1 from server1 send message to user2 which is in server2, will work ).

For socket - user association with multiple process, you can join Room with their userId, on authentication, join room like socket.join('myuserId');

and when to send message to that user, you can use io.to('myuserId').emit('message', "Hi How are you?"):

like image 20
Hiren S. Avatar answered Sep 27 '22 22:09

Hiren S.


You're missing the most important part... Your code has to verify the usr is who he says he is. Plain and simple. I've done this multiple ways:

If users are logging in via PHP code, I move the session data to a mysql database. I then use a string on the PHP side to generate a response for a challenge to the client, who sends it to my web socket server. The WS server will challenge the client and look up the session information in the mysqldb. Done.

In my more recent developments, the actual login process is done via the web socket server. I verify the user credentials via whatever DB (in my instance, MySQL) and tie the username to the socket. Finished...

Do not purely rely on the javascript-based site to say "My name is." Otherwise, as you said, user impersonation becomes a walk in the park. You MUST validate that the user is who he says he is IF you're implementing a system where that matters. "Web sockets" themselves are not magical components that do this for you.

like image 34
Jonny Whatshisface Avatar answered Sep 27 '22 21:09

Jonny Whatshisface