Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Article on Session-based Authorization with Socket.IO #77

Merged
merged 7 commits into from

4 participants

@shaharke

Hi,

I've written a short how-to on configuring socket.io to perform session-based authorization based on Express' session.

Hope you find it interesting.

Shahar

@creationix
Owner

@kriskowal, @pgte, @JoshuaGross, @nrstott, @gopherkhan, would one of you have time to review this article?

@nrstott
Collaborator
@gopherkhan
Collaborator
@nrstott
Collaborator

I'm reviewing it now.

@nrstott nrstott was assigned
@nrstott
Collaborator

It's a good article. One thing, please format the markdown so that a 'description' will appear on the front page. Currently there is none and that makes the front page of the site look a bit off. When you've done that I'll be happy to review again and merge.

edit: after reviewing your 70-line elaborately commented snippet, I withdraw my previous statement requesting more explanation

Shahar Kedar Fixing typos 4a59f30
@shaharke

Ah.. saw your last comment only after the push. Anyway, I feel it will look better with a description and I tried to keep it short and to the point.
I also fixed some typos.

@nrstott nrstott merged commit 66fba7f into creationix:master
@shaharke

Thanks guys! Hope to add more content in the future.

@creationix
Owner

Yay, the auto-publish hook is still working!

@nrstott
Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 20, 2012
  1. Global authorization in socket-io using Express' session id: committi…

    Shahar Kedar authored
    …ng code base
  2. Added my author markdown

    Shahar Kedar authored
  3. Started working on the article's markdown

    Shahar Kedar authored
  4. Code styling

    Shahar Kedar authored
Commits on Dec 28, 2012
  1. Fixing typos

    Shahar Kedar authored
  2. Added a description section

    Shahar Kedar authored
This page is out of date. Refresh to see the latest.
View
79 articles/socket-io-auth.markdown
@@ -0,0 +1,79 @@
+Title: Session-based Authorization with Socket.IO
+Author: Shahar Kedar
+Date: Thu Dec 12 2012 17:44:00 GMT+0200
+Node: v0.8.15
+
+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](https://github.com/LearnBoost/socket.io/wiki/Authorizing) 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...).
+
+## 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](http://socket.io/#how-to-use))
+
+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](/socket-io-auth/server.js) 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:
+
+<socket-io-auth/index.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:
+
+<socket-io-auth/express-snippet.js>
+
+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 <code>node server.js</code> and open your browser at [http://localhost:3000](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-auth/socket-io-snippet.js>
+
+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 <code>io.set('authorization', function (handshakeData, accept)</code> 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 <code>connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], 'secret')</code>, 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
14 articles/socket-io-auth/express-snippet.js
@@ -0,0 +1,14 @@
+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);
View
22 articles/socket-io-auth/index.html
@@ -0,0 +1,22 @@
+<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>
View
11 articles/socket-io-auth/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "socket-io-auth",
+ "version": "0.0.1",
+ "private": true,
+ "dependencies": {
+ "express": "3.0.5",
+ "socket.io" : "0.9.13",
+ "connect" : "2.7.1",
+ "cookie" : "0.0.5"
+ }
+}
View
69 articles/socket-io-auth/server.js
@@ -0,0 +1,69 @@
+// Imports
+var io = require('socket.io')
+, http = require('http')
+, express = require('express')
+, cookie = require('cookie')
+, connect = require('connect');
+
+
+// Create Express
+var app = express();
+
+// Configure Express app with:
+// * Cookie parser
+// * Session manager
+app.configure(function () {
+ app.use(express.cookieParser());
+ app.use(express.session({secret: 'secret', key: 'express.sid'}));
+ });
+
+// Configture GET '/' to return index.html
+app.get('/', function (req, res) {
+ res.sendfile(__dirname + '/index.html');
+});
+
+// Create HTTP server on port 3000 and register socket.io as listener
+server = http.createServer(app)
+server.listen(3000);
+io = io.listen(server);
+
+// Configure global authorization handling. handshakeData will contain
+// the request data associated with the handshake request sent by
+// the socket.io client. 'accept' is a callback function used to either
+// accept or reject the connection attempt.
+// We will use the session id (attached to a cookie) to authorize the user.
+// in this case, if the handshake contains a valid session id, the user will be authorized.
+io.set('authorization', function (handshakeData, accept) {
+ // check if there's a cookie header
+ if (handshakeData.headers.cookie) {
+ // if there is, parse the cookie
+ handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
+ // the cookie value should be signed using the secret configured above (see line 17).
+ // use the secret to to decrypt the actual session id.
+ handshakeData.sessionID = connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], 'secret');
+ // if the session id matches the original value of the cookie, this means that
+ // we failed to decrypt the value, and therefore it is a fake.
+ if (handshakeData.cookie['express.sid'] == handshakeData.sessionID) {
+ // reject the handshake
+ return accept('Cookie is invalid.', false);
+ }
+ } else {
+ // if there isn't, turn down the connection with a message
+ // and leave the function.
+ return accept('No cookie transmitted.', false);
+ }
+ // accept the incoming connection
+ accept(null, true);
+});
+
+// upon connection, start a periodic task that emits (every 1s) the current timestamp
+io.on('connection', function (socket) {
+ var sender = setInterval(function () {
+ socket.emit('data', new Date().getTime());
+ }, 1000)
+
+ socket.on('disconnect', function() {
+ clearInterval(sender);
+ })
+
+});
View
20 articles/socket-io-auth/socket-io-snippet.js
@@ -0,0 +1,20 @@
+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);
+});
View
6 authors/Shahar Kedar.markdown
@@ -0,0 +1,6 @@
+Github: shaharke
+Email: shahar@gmail.com
+Twitter: shahar_kedar
+Location: Tel Aviv, Israel
+
+I'm a senior software engineer at a cool startup called BigPanda. My core expertise is Java, but you can find me writing stuff in Groovy, Python and as of recent - Node.JS (and JavaScript in general)
Something went wrong with that request. Please try again.