Sometimes it does not make sense to store session within a cookie. It might be a size issue, or it could be sensitive information you do not want to go across the wire with each request. Whatever the reason, another option is to database the session information. Luckily rails makes it really easy to do this via active record store.

Active record store

To start databasing our sessions, we need to setup rails to use the active record store. To enable active record store we need to edit the config/initializers/session_store.rb file. We need to comment out the cookie store option and add the active record store option.

#Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
Dummy::Application.config.session_store :active_record_store

Creating the database

Next step is to create the session database table. To do that we will run the following commands…

rake db:sessions:create
rake db:migrate

Session data

When you create the session each created session will be entered as a record in the database. There will be id, session_id, data, created_at, and updated_at columns. The session_id will stored in a cookie and be used to retrieve the data column which contains the databased session information. The data column will store the information as a base64 encoded string. Like so.

eyJfY3NyZl90b2tlbiI6IlU5V3pTNGp3UmdBcmY4WW5EZ1o1TVVXWmo0U3NQTWczSFJLaDJRZzdZNzg9In0=

Which can be marshaled to a ruby object upon request.

{"_csrf_token"=>"ThiYRHCnUpMAoHFXdOQX8WSCXNXICdgwk49xMkIzP+U=", "last_seen"=>"2012-07-18 16:24:25", "user_id"=>"kadrm002"}

Making rails share

This works pretty well for storing sessions in rails. However, there is a problem. Ruby is using it’s internal marshaler when storing the session information. This means that any other language, such as php, coldfusion, etc cannot decode the data unless you write the marshaler :(

There may however, be a way around this. What if we could override rails and tell it to save the data as base64 encoded JSON instead of marshaling it? Every other language could then decode base64 strings and serialize them from JSON to native objects.

If we again edit our session_store.rb file, we can override some internal rails methods when handling sessions. Below we are overriding base the SessionStore class.

module ActiveRecord
  class SessionStore < ActionDispatch::Session::AbstractStore
    module ClassMethods # :nodoc:
      def marshal(data)
        ActiveSupport::Base64.encode64( ActiveSupport::JSON.encode(data) ) if data
      end

      def unmarshal(data)
        ActiveSupport::JSON.decode( ActiveSupport::Base64.decode64(data) ) if data
      end

      def drop_table!
        connection_pool.clear_table_cache!(table_name)
        connection.drop_table table_name
      end

      def create_table!
        connection_pool.clear_table_cache!(table_name)
        connection.create_table(table_name) do |t|
          t.string session_id_column, :limit => 255
          t.text data_column_name
        end
        connection.add_index table_name, session_id_column, :unique => true
      end
    end
  end
end

If you notice we are only overriding the marshal and unmarshal methods to preform a JSON.encode and JSON.decode instead of the traditional Marshal.load and Marshal.dump.

We can then easily read this information out in another language.

// Coldfusion
<cfset decoded_session = ToString( BinaryDecode( data, "Base64" ) )>
<cfset session_struct = DeserializeJSON( decoded_session )>

// PHP
$decoded_session = base64_decode(data);
$session_object = json_decode($decoded_session);