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