How to change the domain for cookies in a filter

Using domain names to separate accounts is becoming more and more popular. 37Signals does it, Blinksale does it and one of our clients does it. In fact, our client goes one step further and allows you to use a custom domain. I would love it if blinksale would let me use invoices.elevatedrails.com. While it sounds really easy, there are some tricky problems.

In our case, these problems come mainly from sessions. We don’t require our users to login on their own domain. In fact, they go to the main page of the application to log in for administrative work. This means that we can’t use the visited domain for the cookie. We need to use a parent domain. To make it concrete: You visit www.app.com to login, and it sends you to mydomain.app.com. Because we use wildcard SSL, all admin functions are done on the mydomain.app.com domains.

That’s pretty straightforward. In our production.rb file, we have:

ActionController::Base.session_options[:session_domain] = '.app.com'

At the same time, we allow our users to set up a CNAME so that their site can be accessed by a custom domain. For instance, they can have customdomain.com point to mydomain.app.com. We really like the branding this gives. It plays havoc with sessions, since all cookies are set for .app.com

We have a filter that finds the account based upon the domain and does some basic setup. I figured it would be trivial to change the cookie domain in this filter by using:

ActionController::Base.session_options[:session_domain] = '.customdomain.com'

That doesn’t work at all. By the time our filter is executed, the outbound session cookies have already been created. They live inside the @cgi variable in the request object. While rails doesn’t give us any way of changing these cookies, we can use the brute force ruby approach. My solution is:

  def set_cookie_domain(domain)
    my_cgi = request.instance_eval "@cgi"
    ocookies = my_cgi.instance_eval("@output_cookies")
    ocookies.each do |cookie|
      cookie.domain=domain
    end 
  end

Here, I use instance_eval to get at instance variables in objects that don’t otherwise make them accessible. Once I get the list of cookies, I can go through and change the domain of each. Yes, it breaks encapsulation, but it also fixes my problem.

The solution is relatively simple, but the process of figuring this out was really tricky. The fact that I could find the problem in fix it in 90 minutes is due in large part to the fact that the source code for Rails is available. I love working with open source technologies because it gives me the power to work around these kind of limitations.