Eric
Eric

Reputation: 2884

Rack: Multiple session cookies for a single rack application

How can one interact with multiple sessions cookies (for different path or domains) in a single rack application?

For example, considering the following application using 3 locations:

Should be able to interact with 3 sessions cookies:

Rack::Session::Cookie seemed to be a good choice, but as a middleware the session cookie has to be set in config.ru and seems to be limited to one session cookie per rack application.

In this special case, the main rack application point is to easily add sub applications, so dividing the application in multiple rack application to use Rack::Session::Cookie is not a viable solution.

The ideal would be a way to interact freely with multiple session cookies from inside the rack application code.

For now, I am considering:

But both are quite tedious so I was wondering if there was a simpler way to achieve this functionality.

Thanks in advance for any advice or suggestion.

Upvotes: 4

Views: 1752

Answers (1)

Eric
Eric

Reputation: 2884

In case anyone run with the same needs, I found that making a class to managing sessions inside the application was the easiest way to go.

Rack::Utils have 2 nice shortcuts, Rack::Utils.set_cookie_header! and Utils.delete_cookie_header! that can really make the things easier when dealing with cookies.

I am saving the sessions in the database used my application, but it should be trivial to support another back-end.

As a side note, a few considerations I came with:

  • To be sure to the name of the cookie is valid, I am using a sha-1 of the sub-application name for that purpose.
  • SecureRandom.urlsafe_base64 is useful for generating session key.
  • Sessions cleaning and refreshing has to be implemented manually.

Sample code

The class managing the cookies, the commit function set and delete the cookies to rack.

class Framework::Response::Cookies

  def set( params )
    @cookies[params.delete( :name )] = params
  end

  def remove( params )
    @remove_cookies[params.delete( :name )] = params
  end

  def commit( headers )
    @cookies.each_pair do |name, params|
      Rack::Utils.set_cookie_header!( headers, name, params )
    end
    @remove_cookies.each_pair do |name, params|
      Rack::Utils.delete_cookie_header!( headers, name, params )
    end
  end

end

The class managing the session (using Mongo as a backend):

class Database::Mongo::Session < Session

  def save
    expire = Time.now + @session_ttl
    @framework.content.db.find_and_modify( self.collection_name, { 
      :query => { :name => @session_name, :id => @session_id }, 
      :update => { :expire => expire, :name => @session_name, :id => @new_session_id || @session_id , :variables => @variables.to_hash },
      :upsert => true 
    })
    @framework.response.cookies.set( 
      :name => @session_name,
      :value => @new_session_id || @session_id,
      :path => @framework.applications.active.active_request['path'],
      :domain => @framework.applications.active.active_request['host'],
      :httponly => true  
    )
  end

  def delete
    @framework.content.db.remove( self.collection_name, { :id => @session_id } )
    @framework.response.cookies.remove( :name => @session_name )
  end

end

Each time @framework.response.cookies.set is called, it pushes a cookie data to the Framework::Response::Cookies class @cookies variable.

Before serving the response, a call to Framework::Response::Cookies.commit commit the cookies using Rack::Utils.

Upvotes: 3

Related Questions