Socket.IO client authentication using Laravel 5 session data

Recently I had a need for rapid notifications of the user about the specific changes in the MySQL database. So I decided to use Socket.IO which I already used before in similar situations. The only problem was that I had to solve how to check if the connected Socket.IO client was authenticated in my Laravel 5 application and get its id to show specific data

Complete code example on GitHub

So the basic idea is to grab Laravel 5 auth cookie, decode it in NodeJS (Socket.IO) and get the user id from it.

Laravel 5 with Redis

Laravel 5 supports Redis "from the box" so only thing you need to do is add support package and then configure the Redis.

Predis

First you need to install Predis. To do this simply run this composer command composer require predis/predis

Laravel Cookie and CSRF Token

Now we need to pass Laravel Cookie and CSRF token to Socket.IO when browser tries to connect. The only thing you need to notice here is that by default Laravel cookie is http only which means that it will only be accessible through the HTTP protocol. You can either change Laravel's config to use simple cookie or simple use raw PHP once. I prefer to use raw PHP once.

So first we define all need parameters somewhere in Blade like this:

const SOCKET_PARAMS  = 'laravel_session_cookie={{ $_COOKIE[config('session.cookie')] }}&csrf_token={{ csrf_token() }}'

Then when starting the connection just add parameters like this:

var socket = io('http://localhost:3000', {query: SOCKET_PARAMS})

Socket.IO Auth Check

Firstly we need to create the list of necessary configs for decoding Laravel session.

const config = {
    'secret_key': new Buffer('CYR+MUBTKK/gkL7y6FhPeQqhAmv8vmUeNqUw6qGqNcE=', 'base64'),
    'auth_class': 'Illuminate\\Auth\\SessionGuard', // Don't escape to encode slashes...
    'auth_name' : 'login_web_'
}

Now we need to define middleware function which will give us request data to play with.

let io = require('socket.io')()
io.use(function(socket, next) {
    // Here all the magic will be...
})

First we need to decode cookie and get session id

try {
    let cookie = JSON.parse(new Buffer(socket.handshake.query.laravel_session_cookie, 'base64'))
} catch(err) {
    return next('Cookie is invalid', false)
}

let iv    = new Buffer(cookie.iv, 'base64');
let value = new Buffer(cookie.value, 'base64');

let rij_cbc = new mcrypt.MCrypt('rijndael-128', 'cbc');
    rij_cbc.open(config.secret_key, iv);

let decrypted = rij_cbc.decrypt(value).toString();

let len = decrypted.length - 1;
let pad = decrypted.charAt(len).charCodeAt(0);

let session_id = PHPUnserialize.unserialize(decrypted.substr(0, decrypted.length - pad));

Now we have session_id and can get session data from Redis (or file or somewhere else depending on where you store it).

session_id = 'laravel:' + session_id

redis.getClient().get(session_id, function(err, session) {
    try {
        session = PHPUnserialize.unserialize(PHPUnserialize.unserialize(session))
    } catch (err) {
        return next('Error unserializing session', false)
    }
})

Now is the right time to check for CSRF.

if (session.hasOwnProperty('_token') && session._token !== socket.handshake.query.csrf_token) {
    let message = 'CSRF token is invalid'

    console.log(message)

    return next(message, false)
}

Ok, we good. The last thing we need to do is grab the user id from session. For this we need to create the key in a special format like Laravel does.

let user_key = config.auth_name + crypto.createHash('sha1').update(config.auth_class).digest('hex')
console.log(session[user_key])

The complete source code of this tutorial can be found here on GitHub

Fork me on GitHub