Cookie handling in multi-domain applications in Ruby on Rails

My team is working on a multilingual multinational application in Rails (I know this sounds incredibly pushy but bear with me). The application is at susuh.de if you want to have a look at it, but it’s still alpha version.

In practice, this multi-multi-blah-blah means that we want to use both various national domains (like susuh.at, susuh.pl — those links don’t function yet, they are only examples) and various subdomains (like pl.susuh.de or en.susuh.pl). The national domains will filter content only to respective country (so susuh.de will only have content from Germany) and the subdomains will be used to change the default language for the national domain (so if you are an English-speaking person living in Germany, en.susuh.de is your place to go). The subdomain will also be important in multilingual countries like Switzerland.

Trying to implement this scheme we’ve run into problems with cookie domain handling in Rails. And cookie problems lead to session problems for users, like being erroneously logged out or loosing other settings.

Cookie Monster
Mandatory Cookie-monster reference (you wouldn’t believe how many hits from Google this provides).

It seems that there are two official (normal) ways of handling this — at least that’s what Google says. Either you set your default session options in environment.rb like this:

ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_domain] = 
  '.susuh.de'

Or you don’t set it at all.

The former lets you set cookies for any subdomain of susuh.de, like pl.susuh.de, but no other domain. The latter sets cookie independently for each subdomain, causing different cookies for pl.susuh.de, en.susuh.de and susuh.pl. Neither is what we really want.

Spice Girls
So tell me what you want, what you really really want?

What we really want is to have the same cookie for any subdomain of susuh.eu and a different one for any subdomain of susuh.pl and so on. Nota bene, I’m not talking about a Single Sign-On problem, this is on much more basic level. We tried experimenting with setting DEFAULT_SESSION_OPTIONS “on the fly” in before_filter, but found out that this is too late (basically, this sets cookie domain for next request).

Fortunately, all is not lost. If all else fails, start reading code. And that’s what I did. The “entry point” for Rails is defined in dispatcher.rb file. Glancing over the code, I noticed something new for me: a before_dispatch callback. Google agreed that this may be a way to go, giving us link to http://m.onkey.org/2007/10/16/dispatcher-callbacks, among others.

Here’s our solution:

require 'dispatcher'
module ActionController
  class Dispatcher
    def set_session_domain
      ApplicationController.session_options.update(
        :session_domain => "#{@request.host.gsub(/^[^.]*/, '')}"
      )
    end
    
    before_dispatch :set_session_domain
  end
end

19 responses to “Cookie handling in multi-domain applications in Ruby on Rails

  • rdempsey

    Very cool, thanks. Any advice on supporting multiple languages (globalization) in a single Rails application would be greatly appreciated too. I know that it used to be a PITA to support multiple languages (keep all copy in the database in each language). Has it gotten any better?

  • szeryf

    rdempsey: We use GetText. It stores its translations in special files. It’s not that bad. At least most of the translation work can be done without assistance of programmers.

    I think that maintaining several language version of any application is quite a lot work. There are several plugins of this type but since I haven’t tried any other of them (except GetText) I can’t tell if they are much better or worse.

  • Jacek Becela

    We’ve used GetText in the past but hell, it feels do ’97. Try Gibberish. Much simpler, No compilation, no .po, .mo madness.

  • Aaron

    This now fails in Edge Rails with:
    uninitialized constant ActionController::Dispatcher::ApplicationController

    not sure why

  • Eduardo Bellani

    Hey guys.
    If anybody is having trouble with rails 2.0.2, I’ve come up with a working hack(and also changed some cosmetic stuff)

    require 'activesupport' 
    require 'dispatcher'
    module ActionController
      class Dispatcher
        def set_session_domain
          ActionController::Base.session_options[:session_domain] = "#{@request.domain}"
        end 
        before_dispatch :set_session_domain
      end
    end
    
  • Mat

    Great post – as well as the internationalization bit, this is helpful for anyone who uses subdomains but deploys to different top-level domains for development, testing, and production.

    One important fact (explained in the linked “localhost & cookies” post above) is that this won’t work for localhost – in fact, if you try, your session won’t be saved at all, and you’ll have InvalidAuthenticityToken problems if you use authenticity checking.

    Also, I think it’s always better to extend classes rather than reopen them – so, with those two things in mind, here’s a slight refactor (works fine in Rails 2.1):

    module ActionControllerExtensions
      def self.included(base)
        base::Dispatcher.send :include, DispatcherExtensions
      end
      
      module DispatcherExtensions
        def self.included(base)
          base.send :before_dispatch, :set_session_domain
        end
        
        def set_session_domain
          ApplicationController.session_options.update :session_domain => "#{@request.host.gsub(/^[^.]*/, '')}" unless request.host.match /\.localhost$/
        end
      end
    end
    
    ActionController.send :include, ActionControllerExtensions
    
  • Luke Francl

    This is an interesting technique, I wish I had seen this earlier.

    I tried to do a similar thing by hacking Rails session code so that the session domain could be specified with a Proc in the controller. It worked for sending cookies, but I was having trouble reconstituting them.

    For developing on localhost with subdomains, I also ran into the invalid authenticity token problems, which we just worked around by creating an alias in /etc/hosts and accessing the site from there, with the session domain configured appropriately of course.

  • links for 2008-07-17 | Libin Pan

    […] Cookie handling in multi-domain applications in Ruby on Rails « require ‘brain’ Cookie handling in multi-domain applications in Ruby on Rails (tags: cookies development programming rails ror ruby scalability subdomain rubyonrails sessions cookie) […]

  • Subdomains and rails « Jabberwocky

    […] out throughout your site (regardless of subdomains), you could use a small amendement as in this link.  However, that may not be what you need – sometimes you want to keep the worlds separated, so […]

  • Brendan Schwartz

    Adjusted to work with Rails 2.3. Hopefully this will save someone a 30 minute code dive.

    module ActionControllerExtensions
      def self.included(base)
        base::Dispatcher.send :include, DispatcherExtensions
      end
    
      module DispatcherExtensions
        def self.included(base)
          base.send :before_dispatch, :set_session_domain
        end
    
        def set_session_domain
          domain = @env['HTTP_HOST'].gsub(/:\d+$/, '').gsub(/^[^.]*/, '')
          puts "DOMAIN: #{domain}"
          @env['rack.session.options'].update :domain => domain
        end
      end
    end
    
    ActionController.send :include, ActionControllerExtensions
    
  • Brendan Schwartz

    ^ Minus the puts "DOMAIN: #{domain}" line of course.

  • szeryf

    Brendan: thanks, much appreciated.

  • szeryf

    None of the above works with the version of Rails that you use? See http://rubista.info/post/88299744/varios-dominios-e-uma-unica-aplicacao-rails-com for more examples (no, I don’t understand this article either :)

  • Brendan Schwartz

    If anyone stumbles upon this article, I’ve updated this code to work with Rails 2.3.2. See: http://gist.github.com/64874

  • kareem

    thanks for updating your code for 2.3.2 , brendan

  • Michal

    If your using Rails 2.3 or any other framework that utilizes Rack, this could be useful:
    http://codetunes.com/2009/04/17/dynamic-cookie-domains-with-racks-middleware/

  • Changing a Cookie’s Domain in Rails | Matt's Blog

    […] you want to change your session cookie’s path globally, there’s a documented setting. But what if you want to set some one-off cookie to be from something other than your […]

  • Aleksey

    The proposed method with @env and class variables will not work for threaded applications. Currently that may be not a problem, as 99.9% applications on Rails are deployed as single-thread model. But soon that may change, and this code will generate hard to debug errors due to thread unsafety

  • Paweł Gościcki (@pawelgoscicki)

    How about doing this? In `config/environments/production.rb`:
    config.session_store :cookie_store, { :key => ‘_my_app_session’, :domain => “.myapp.com” }

Leave a comment