Sharing Sessions and Authentication Between Rails and Node.js Using Redis

Recently, I’m trying to implement a real-time notification feature to our Rails application. Socket.IO from Nodejs is really a good choice, and since I’ven wrote pretty much code using Eventmachine, it’s really not hard to understand it.

Turns out the real ‘bitching’ part is how to share sessions information and do authentication between rails and socket.io. Firstly, you can not directly read rails’ sessions from cookie in nodejs, it’s encrypted.  Secondly, even if you managed to use redis-store  to store sessions into redis, it’ll still fail to get the correct content. As discussed in this pull request , redis-store serialize data using Marshal.dump and Marshal.load , and nodejs is unable to recognize them. Node.js will always try to parse them as JSON. I’ve tried so hard to find a marshal parser in javascript world, and also tried to replace Marshal with JSON in redis-store, but none of them is an easy work and it causes some other troubles to our existing Rails app.

So, finally, after some experiment, here is my workaround. Let Rails store session into cookie as default, no need to change it,  however, I manually create a copy of session data in redis after user sign in, and expire them after user sign out. In order to that, I override two methods provided by Devise inside of ApplicationController:

# app/controllers/application_controller.rb
def after_sign_in_path_for(resource_or_scope)
  #store session to redis
  if current_user
    # an unique MD5 key
    cookies["_validation_token_key"] = Digest::MD5.hexdigest("#{session[:session_id]}:#{current_user.id}")
    # store session data or any authentication data you want here, generate to JSON data
    stored_session = JSON.generate({"user_id"=> current_user.id, "username"=>current_user.screen_name, ... ...})
    $redis.hset(
      "mySessionStore",
      cookies["_validation_token_key"],
      stored_session,
     )
   end
end

def after_sign_out_path_for(resource_or_scope)
  #expire session in redis
  if cookies["_validation_token_key"].present?
    $redis.hdel("mySessionStore", cookies["_validation_token_key"])
  end
end

Basically, when user first signs in, it creates a key called ‘_validation_token_key’ in side of cookies, the value of the key is an unique MD5 string. Meanwhile, this MD5 value is stored into a hash table called ‘mySessionStore’ in redis using ‘hset’. Notice that the corresponding value of this MD5 key in redis is JSONed sessions you want to keep. When user sign out, Devise will call ‘after_sign_out_path_for’ method, and you can delete the hash key inside of redis at that time.

The key part of this code is _validation_token_key stored in cookies, when user connects to Socket.IO, it will try to read this key from cookie, and then fetch the related session value from redis. You can easily use this to do client authentication across rails and nodejs. The example code on Node.js server side looks like this:

var io = require('socket.io').listen(3003);
var redis = require('redis');
var cookie = require("cookie"); // cookie parser
var redis_validate = redis.createClient(6379, "127.0.0.1");

io.configure(function (){
  io.set('authorization', function (data, callback) {

    if (data.headers.cookie) {

      data.cookie = cookie.parse(data.headers.cookie);
      data.sessionID = data.cookie['_validation_token_key'];

      // retrieve session from redis using the unique key stored in cookies
      redis_validate.hget(["mySessionStore", data.sessionID], function (err, session) {

        if (err || !session) {
          return callback('Unauthorized user', false);
        } else {
          // store session data in nodejs server for later use
          data.session = JSON.parse(session);
          return callback(null, true);
        }

      });

    } else {
      return callback('Unauthorized user', false);
    }
  });
});

... ...

After this, if the client passes the authentication, you can easily access session values on ‘connection’ callbacks, like this:

io.sockets.on('connection', function(client){

  var user_id = client.handshake.session['user_id'];
  var username = client.handshake.session['username'];
  ... ...
});
About these ads

2 comments

  1. Steven Yue

    Even if the session name is encrypted, hacker can still fake user’s information if they can sniff user’s cookie. Right now the session is cleaned after user sign out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s