SocketIO + MySQL Authentication

authentication, node.js, socket.io, websocket

Solution

You're close, though I'd recommend setting a request header over setting a query string param. The `data` variable in your authorization function is handshake data that contains request header and cookie information you can use. Here's an example with setting a cookie:

On the server

io.configure(function() {
    io.set('authorization', function(handshake, callback) {
        var cookie, token, authPair, parts;

        // check for headers
        if (handshake.headers.cookie && 
            handshake.headers.cookie.split('=')[0]=='myapp') {

            // found request cookie, parse it
            cookie   = handshake.headers.cookie;
            token    = cookie.split(/\s+/).pop() || '';
            authPair = new Buffer(token, 'base64').toString();
            parts    = authPair.split(/:/);

            if (parts.length>=1) {
                // assume username & pass provided, check against db
                // parts[0] is username, parts[1] is password
                // .... {db checks}, then if valid....
                callback(null, true);
            } else if(parts.length==1) {
                // assume only username was provided @ parts[0]
                callback(null,true);
            } else {
                // not what we were expecting
                callback(null, false);
            }
        }
        else {
            // auth failed
            callback(null, false);
        }
    });
});

On the client

Before you call `socket.connect`, set a cookie with your auth / user info:

function writeCookie(value, days) {
    var date, expires;

    // days indicates how long the user's session should last
    if (days) {
        date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        expires = "; expires="+date.toGMTString();
    } else {
        expires = "";
    }
    document.cookie = "myapp="+Base64.encode(value)+expires+"; path=/";
};

// for a 'viewer' user:
writeCookie('usernameHere', 1);

// for the 'host' user:
writeCookie('usernameHere:passwordHere', 1);

You'll need a Base64 library on the client side unless your browser supports `btoa()`.

It's important to note that this isn't a good authentication structure. Passing user credentials straight in either query strings or header information is not secure. This method gets you closer to a more secure method, though. I'd recommend looking into an auth library like passport.js or everyauth. You can sub-in this code to utilize the session information those libraries store in running your checks.

Problem

I am trying to authenticate over a MySQL DB via socketIO. I have established the connection and can query results without an issue, but for some reason I cannot pass whether or not the user is authenticated into the `connection` part of socketio. The idea is my app has hosts and viewers. If connecting to the app without sending a password in the `QueryString` the app assumes its a viewer and accepts connection. If a password is sent, it is checked against the DB and accepts/rejects the connection. I want a variable to pass into the `connection` so I can use it inside of my apps events. Here's what I have so far but apparently the `data.query['ishost']` isn't passing into the app. ``` sio.configure(function() { sio.set('authorization', function (data, accept) { UserID = data.query['username']; try { UserID = UserID.toLowerCase(); } catch(err) { return accept("No WebBot Specified. ("+err+")", false); } // if not sending a password, skip authorization and connect as a viewer if (data.query['password'] === 'undefined') { return accept(null, true); } // if sending a password, attempt authorization and connect as a host else { client.query( 'SELECT * FROM web_users WHERE username = "'+UserID+'" LIMIT 1', function selectCb(err, results, fields) { if (err) { throw err; } // Found match, hash password and check against DB if (results.length != 0) { // Passwords match, authenticate. if (hex_md5(data.query['password']) == results[0]['password']) { data.query['ishost'] = true; accept(null, true); } // Passwords don't match, do not authenticate else { data.query['ishost'] = false; return accept("Invalid Password", false); } } // No match found, add to DB then authenticate else { client.query( 'INSERT INTO web_users (username, password) VALUES ("'+UserID+'", "'+hex_md5(data.query['password'])+'")', null); data.query['ishost'] = "1"; accept(null, true); } client.end(); } ); // Should never reach this return accept("Hacking Attempt", false); } // Definitely should never reach this return accept("Hacking Attempt", false); }); }); ``` Writing to the `data.query` makes it accessible through handshakeData. But for some reason its not passing it through the app. Any help is appreciated, thank you.

Original source