What does Rails 3 session_store domain :all really do?

27,141

Solution 1

OK, the way to accomplish this is to set the domain on the session cookie dynamically. To do this early enough it should be done as rack middleware:

# Custom Domain Cookie
#
# Set the cookie domain to the custom domain if it's present
class CustomDomainCookie
  def initialize(app, default_domain)
    @app = app
    @default_domain = default_domain
  end

  def call(env)
    host = env["HTTP_HOST"].split(':').first
    env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{@default_domain}"
    @app.call(env)
  end

  def custom_domain?(host)
    host !~ /#{@default_domain.sub(/^\./, '')}/i
  end
end

Solution 2

I didn't think any of the existing answers directly answered the question in the title so I wanted to chip in.

When the client (browser) goes to a website, the website tells the client to set a cookie. When it does so, it specifies the cookie name, value, domain, and path.

:domain => :all tells Rails to put a dot in front of the cookie domain (which is whatever host your browser has browsed to), such that the cookie applies to all subdomains.

Here is the relevant code from Rails 4.1 (actionpack/lib/action_dispatch/middleware/cookies.rb):

  def handle_options(options) #:nodoc:
    options[:path] ||= "/"

    if options[:domain] == :all
      # if there is a provided tld length then we use it otherwise default domain regexp
      domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP

      # if host is not ip and matches domain regexp
      # (ip confirms to domain regexp so we explicitly check for ip)
      options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
        ".#{$&}"
      end
    elsif options[:domain].is_a? Array
      # if host matches one of the supplied domains without a dot in front of it
      options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
    end
  end

I see you've already answered the second part of your question about allowing subdomains to have separate sessions.

Solution 3

tl;dr: Use @Nader's code. BUT I found I needed add it into my conifg/environments/[production|development].rb and pass my dot-prefixed-domain as an argument. This is on Rails 3.2.11

Cookie sessions are usually stored only for your top level domain.

If you look in Chrome -> Settings -> Show advanced settings… -> Privacy/Content settings… -> All cookies and site data… -> Search {yourdomain.example} You can see that there will be separate entries for sub1.yourdomain.example and othersub.yourdomain.example and yourdomain.example

The challenge is to use the same session store file across all subdomains.

##Step 1: Use @Nader's CustomDomainCookie code##

This is where Rack Middleware comes in. Some more relevant rack & rails resources:

Basically what this does is that it will map all of your cookie session data back onto the exact same cookie file that is equal to your root domain.

##Step 2: Add To Rails Config##

Now that you have a custom class in lib, make sure are autoloading it. If that meant nothing to you, look here: Rails 3 autoload

The first thing is to make sure that you are system-wide using a cookie store. In config/application.rb we tell Rails to use a cookie store.

# We use a cookie_store for session data
config.session_store :cookie_store,
                     :key => '_yourappsession',
                     :domain => :all

The reason this is here is mentioned here is because of the :domain => :all line. There are other people that have suggested to specify :domain => ".yourdomain.example" instead of :domain => :all. For some reason this did not work for me and I needed the custom Middleware class as described above.

Then in your config/environments/production.rb add:

config.middleware.use "CustomDomainCookie", ".yourdomain.example"

Note that the preceding dot is necessary. See "https://stackoverflow.com/questions/3865345/sub-domain-cookies-sent-in-a-parent-domain-request" for why.

Then in your config/environments/development.rb add:

config.middleware.use "CustomDomainCookie", ".lvh.me"

The lvh.me trick maps onto localhost. It's awesome. See this Railscast about subdomains and this note for more info.

Hopefully that should do it. I honestly am not entirely sure why the process is this convoluted, as I feel cross subdomain sites are common. If anyone has any further insights into the reasons behind each of these steps, please enlighten us in the comments.

Solution 4

This option is used to make sure the application will be able to share sessions across subdomains. The :all option assumes that our application has a top-level domain size of 1. If not then we can specify a domain name instead and that will be used as the base domain for the session.

Solution 5

I deploy an application that is hosted under www.xyz.example and under xyz.example.

For me, :domain => :all sets the domain for the session-cookie to xyz.example. So not for a top-level domain but for 1 level above the tld.

Share:
27,141
Nader
Author by

Nader

cto @ kapost husband & father traveller & photographer

Updated on March 11, 2020

Comments

  • Nader
    Nader about 4 years

    Updated question to make it more clear

    I understand that you can set the domain of your session_store to share sessions between subdomains like this: Rails.application.config.session_store :cookie_store, :key => '_my_key', :domain => "mydomain.com"

    in Rails 3, what does the setting :domain => :all do? It can't let you share sessions across top-level domains, cookies can't do that. The documentation says it assumes one top level domain. So what happens if multiple domains access your app?

    In my app, my users can create personal subdomains of one main domain, but then can also access that subdomain via their own custom domain.

    What is the correct session_store domain setting so that I can: a) share sessions across all domains of my primary domain, eg "mydomain.com" b) users who access their personal subdomain eg "user1.mydomain.com" via a CNAME custom url like "some.otherdomain.com" can still create separate sessions.

    Thanks

  • Nader
    Nader over 13 years
    Rishav: when I specify a domain like 'mydomain.com' that works for all subdomains, but if a user comes into the app via a custom domain it doesn't seem to create a session using :all. What's the correct way to handle that?
  • anfff_g
    anfff_g over 13 years
    As I said, domain => :all assumes 1 top-level domain. In your case is it like "foo.com" and "bar.com" .. come to same instance of your rails ? and you want share sessions b/w the domains ?
  • Nader
    Nader over 13 years
    In my app each user has a subdomain of foo.com Some users also ave custom domains like bar.com pointing at their foo.com subdomain So yes ideally I'd share sessions between all domains, but that is not critical, as long as sessions at least are shared between each top level domain.
  • Gnomet
    Gnomet over 11 years
    Thanks for this, I had exactly the same situation and this helped! If others are (like me) adding Rack Middleware for the first time, the Railscast at: railscasts.com/episodes/151-rack-middleware can be helpful. And if adding to Rails 3 application, see about load paths at: stackoverflow.com/questions/11478920/…
  • Tony
    Tony about 11 years
    Hey, it didnt work for me. Can you help me? My question is in stackoverflow.com/questions/14780178/…
  • Daniel
    Daniel about 9 years
    I had to wrap a begin/rescue/end around the env["HTTP_HOST"] due to people messing with the host header (hackers).
  • bhaity
    bhaity almost 9 years
    Evan, this has been useful for me but I'm still unable to get this to work. I'm using rails 4, devise 3.2 and testing on localhost and localtest.me. For some reason, I can't log in using any of my user accounts after implementing this. Changing the session key has no effect.