Static Version

Session-based Authorization with Socket.IO

Finding a decent article about session based authorization in socket.io is more difficult than one expected. This article will show how you can take advantage of Express session middleware and socket.io authorization middleware to create a very simple authorization mechanism when establishing new socket.io connections.

Prolog

I decided to write this article after getting a bit frustrated from searching the Internet for a decent example on how to use session based authorization with socket.io. To be honest, socket.io wiki page on authorization was quite simple to follow and understand, but when it came to session based authorization, I got a bit lost (especially considering the fact that it's my third day using Node...).

Disclaimer: the original concept was published here. Unfortunately the example there didn't work with Express 3.x.x, which is why I decided to publish this post.

Reading Suggestions

Before reading this article, I strongly suggest you get familiar with Express and Socket.IO. I kept things as simple and minimal as possible, so you really don't need more than a couple of hours to learn what needs to be learned if you're a complete newbie.

I also suggest reading the Authorization wiki page referenced in the prolog.

Global Authorization vs Namespace Authorization

First I would like to distinguish between two authorization scopes that are currently supported by socket.io, namely - Global and Namespace. Global authorization will be used to authorize any attempt to open a (socket.io) connection against our Node application. Namespace authorization, on the other hand, allows you to use different authorization rules when accepting connections to a specific socket.io namespace (more on namespaces can be found here)

In this article I will exemplify only how to enable Global authorization, although from the looks of things, namespace authorization is quite straightforward once you understand global authorization.

A Few Words about Timing

It's important to understand that the authorization process takes place during handshake. This means that, prior to authorization, no socket connection is established. As a matter of fact, because the handshakes in socket.io are implemented as HTTP calls, one can use the HTTP context to get relevant information on the user who's trying to connect. As you'll see next, I will be using cookie data to authorize any user that tried to establish a socket connection to the server.

Session-based Authorization

As I said, I will be using cookie data to authorize our John Dow. Specifically, I will be using the user's session id to make sure that indeed this user went through the system. The trick here is to use Express' session middleware to assign a signed session id to the user, so the next time he sends a request (in our case that would be during socket.io handshake) it will be possible to ascertain that this user is a valid user and not a scoundrel. Theoretically, I could also fetch more information about the user using his session id, but I felt that it's out of scope for this article. I admit that this kind of authorization method is naive, but it's good enough to get you started.

Time to Code!

I believe I said more than enough on the subject at hand, and now would be the right time to start looking at code. The complete source code takes less then 70 lines (including elaborate comments) and should be enough for anyone with some experience with Express and Socket.IO. Nonetheless I will guide you through the code so there are no complaints.

Client side: index.html

We begin by creating an HTML file called (surprisingly) index.html:

index.html
<html>
   
<head>
       
<script src="http://localhost:3000/socket.io/socket.io.js" type="text/javascript"></script>
       
<script type="text/javascript">
            tick
= io.connect('http://localhost:3000/');
            tick
.on('data', function (data) {
                console
.log(data);
           
});

            tick
.on('error', function (reason){
              console
.error('Unable to connect Socket.IO', reason);
           
});

            tick
.on('connect', function (){
              console
.info('successfully established a working and authorized connection');
           
});
       
</script>
   
</head>
   
<body>
        Open the browser console to see tick-tocks!
   
</body>
</html>

As you can see, our client side is nothing more then several lines of javascript establishing a socket.io connection with our local server. Once connection is established, you will be able to see time slipping away between your fingertips (millisecond ticks) inside your browser console.

Notice that I'm also listening to the error event, in case the connection cannot be established, or access is denied by the authorization process.

Configuring Express

Now let's create and configure Express:

express-snippet.js
var app = express();

app
.configure(function () {
    app
.use(express.cookieParser());
    app
.use(express.session({secret: 'secret', key: 'express.sid'}));
});


app
.get('/', function (req, res) {
    res
.sendfile(__dirname + '/index.html');
});

server
= http.createServer(app)
server
.listen(3000);

Pay special notice to the the fact that we're using the session middleware that ships with Express to support sessions. This middleware is responsible for generating session ids for new users when they first login to the system.

The session middleware uses a secret value to sign the session id. We will use this mechanism to our advantage when we try to authorize an incoming socket.io connection.

The lines following the Express configuration code are responsible for serving our precious index.html when a user connects to the server.

The last two lines construct an HTTP server over the Express application, and binding it port 3000. Feel free to run node server.js and open your browser at http://localhost:3000. You should be seeing a single line saying: Open the browser console to see tick-tocks!

Configuring Socket.IO

Next we need to configure socket.io:

socket-io-snippet.js
io = io.listen(server);

io
.set('authorization', function (handshakeData, accept) {

 
if (handshakeData.headers.cookie) {

    handshakeData
.cookie = cookie.parse(handshakeData.headers.cookie);

    handshakeData
.sessionID = connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], 'secret');

   
if (handshakeData.cookie['express.sid'] == handshakeData.sessionID) {
     
return accept('Cookie is invalid.', false);
   
}

 
} else {
   
return accept('No cookie transmitted.', false);
 
}

  accept
(null, true);
});

Now we're getting to the tricky part. First we need to bind socket.io to our HTTP server. That would be the first line of code - piece of cake.

The next block is the reason we're all here - by calling io.set('authorization', function (handshakeData, accept) we instruct socket.io to run our authorization code when performing a handshake.

The logic goes as follows: first we check if there's a cookie associated with the handshake request. In case the user already logged into our system there should be a cookie containing his session id. If no cookie is found the user is rejected.

If a cookie is found, we try to parse it. The crucial line here is connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], 'secret'), which tries to unsign the session id cookie value using the same secret key used for signing. If the cookie was indeed signed by our server than the reverse operation should give us the real session id. This is actually our way of making sure that the user trying to connect is someone we "know" and not some fake user trying to hack into our system. Notice that in case we did not succeed in unsigning the value, the return value should equal to the original value, indicating a problem.

The next couple of lines in the full source code are standard socket.io and I won't go into details explaining them.

Epilogue

That's it! You're ready to implement whatever authorization algorithm you want. Enjoy socket.io!


View the discussion thread.blog comments powered byDisqus