Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

socket.io authentication with sharing session data, how io.use() works

Inspired by How to share sessions with Socket.IO 1.x and Express 4.x? i implemented socket authentication in some "clean" way where is no need to use cookie-parser and to read cookies from headers, but few items remain unclear to me. Example use last stable socket.io version 1.3.6.

var express      = require('express'),
    session      = require('express-session'),
    RedisStore   = require('connect-redis')(session),
    sessionStore = new RedisStore(),
    io           = require('socket.io').listen(server);

var sessionMiddleware = session({
    store   : sessionStore,
    secret  : "blabla",
    cookie  : { ... }
});

function socketAuthentication(socket, next) {
  var sessionID = socket.request.sessionID;

  sessionStore.get(sessionID, function(err, session) {
      if(err) { return next(err); }

      if(typeof session === "undefined") {
          return next( new Error('Session cannot be found') );
      }
      console.log('Socket authenticated successfully');
      next();
  });
}

io.of('/abc').use(socketAuthentication).on('connection', function(socket) { 
  // setup events and stuff
});

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

app.use(sessionMiddleware);
app.get('/', function(req, res) { res.render('index'); });
server.listen(8080);

index.html

<body>
    ...
    <script src="socket.io/socket.io.js"></script>
    <script>
       var socket = io('http://localhost:8080/abc');
    </script>
</body>

So io('http://localhost:8080/abc'); from client-side will send initial HTTP handshake request to server, from where server can gather cookies and many others request informations. So server has access to that initial request via socket.request.

My first question is why handshake request is not in scope of express-session middleware?(More generally in scope of app.use middlewares?) In some way i expected this app.use(sessionMiddleware); to fire before that initial request, and then to access easily to socket.request.session

Second, what are the scenarios in which middlewares defined with io.use() will fire? Only for initial HTTP handshake request? It seems like io.use() is used for socket related stuff(question is: what stuff), while app.use for standard requests.

I'm not quite sure why in the above example io.use() is fired before io.of('/abc').use(). Intentionally i wrote that order putting io.of('/abc').use() first to see will it work and it work. Should have been written conversely.

Lastly, socket.request.res like pointed also from some people in linked question, sometimes is undefined causing app to broke, problem can be solved by providing empty object instead of socket.request.res, like: sessionMiddleware(socket.request, {}, next); which seems to me like a dirty hack. For what reasons socket.request.res yield to undefined?

like image 751
Srle Avatar asked Sep 09 '15 23:09

Srle


People also ask

How does Socket.IO work internally?

Socket.IO allows bi-directional communication between client and server. Bi-directional communications are enabled when a client has Socket.IO in the browser, and a server has also integrated the Socket.IO package. While data can be sent in a number of forms, JSON is the simplest.

Does Socket.IO use WSS?

Note: You can use either https or wss (respectively, http or ws ).

How does Socket.IO connect to client server?

listen(port); // Create a Socket.IO instance, passing it our server var socket = io. listen(server); // Add a connect listener socket. on('connection', function(client){ console. log('Connection to client established'); // Success!


2 Answers

Despite @oLeduc is kind of correct, there are a few more things to explain..

Why the handshake's request is not in scope of express-session middleware?

The biggest reason here is that the middleware in express is designed to handle request specific tasks. Not all, but most of the handlers use the standard req, res, next syntax. And sockets are "request-less" if I can say. The fact that you have socket.request is due to the way the handshake is made, and that it is using HTTP for that. So the guys at socket.io hacked that first request into your socket class so that you can use it. It was not designed by the express team to ever work with sockets and TCP.

What are the scenarios in which middlewares defined with io.use() will fire?

io.use is a close representation of the express use middleware way. In express, the middleware is executed on each request, right? But sockets do not have requests and it will be awkward to use middleware on each socket emit, so they've made it to be executed on each connection. But as well as the express middleware is stacked and used before the actual request is handled (and responded), Socket.IO uses the middleware on connection and even before the actual handshake! You can intercept the handshake if you want to, using that kind of middleware, which is very handy (in order to protect your server from spamming). More on this can be found in the code of passport-socketio

Why io.use() fires before io.of('/abc').use()?

The real explanation on this can be found here, which is this code:

Server.prototype.of = function(name, fn){
    if (String(name)[0] !== '/') name = '/' + name;

    if (!this.nsps[name]) {
        debug('initializing namespace %s', name);
        var nsp = new Namespace(this, name);
        this.nsps[name] = nsp;
    }
    if (fn) this.nsps[name].on('connect', fn);
    return this.nsps[name];
};

And in the beginning of the code, there is this line of code:

this.sockets = this.of('/');

So, there is a default namespace created at the very beginning. And right there, you can see that it has immediately a connect listener attached to it. Later on, each namespace gets the very same connect listener, but because Namespace is EventEmitter, the listeners are added one after another, so they fire one after another. In other words, the default namespace has it's listener at first place, so it fires first.

I don't think this is designed on purpose, but it just happened to be this way :)

Why is socket.request.res undefined?

To be honest, I'm not pretty sure about that. It's because of how engine.io is implemented - you can read a bit more here. It attaches to the regular server, and sends requests in order to make a handshake. I can only imagine that sometimes on errors the headers are separated from the response and that's why you won't get any. Anyways, still just guessing.

Hope information helps.

like image 156
Andrey Popov Avatar answered Sep 30 '22 20:09

Andrey Popov


Why the handshake's request is not in scope of express-session middleware?

Because socket.io will attach to a http.Server which is the layer under express. It is mentioned in a comment in the source of socket.io.

The reason for this is because the first request is a regular http request used to upgrade the reqular stateless http connection into a state-full websocket connection. So it wouldn't make much sense for it to have to go through all the logic that applies to regular http requests.

What are the scenarios in which middlewares defined with io.use() will fire?

Whenever a new socket connection is created.

So every time a client connects it will call the middlewares registed using io.use(). Once the client is connected however, it is not called when a packet is received from the client. It doesn't matter if the connection is initiated on a custom namespace or on the main namespace, it will always be called.

Why io.use() fires before io.of('/abc').use()?

Namespaces are a detail of socket.io's implementation, in reality, websockets will always hit the main namespace first.

To illustrate the situation, look at this snippet and the output it produces:

var customeNamespace = io.of('/abc');

customeNamespace.use(function(socket, next){
  console.log('Use -> /abc');

  return next();
});

io.of('/abc').on('connection', function (socket) {
  console.log('Connected to namespace!')
});

io.use(function(socket, next){
  console.log('Use -> /');

  return next();
});

io.on('connection', function (socket) {
  console.log('Connected to namespace!')
});

Output:

Use -> /
Main namespace
Use -> /abc
Connected to namespace!

See the warning that the socket.io team added to their documentation:

Important note: The namespace is an implementation detail of the Socket.IO protocol, and is not related to the actual URL of the underlying transport, which defaults to /socket.io/….

Why is socket.request.res undefined?

As far as I know it should never be undefined. It might be related to your specific implementation.

like image 26
oLeduc Avatar answered Sep 30 '22 19:09

oLeduc