Share session (cookies) between subdomains in Rails?
Solution 1:
As it turns outs 'domain: all' creates a cookie for all the different subdomains that are visited during that session (and it ensures that they are passed around between request). If no domain argument is passed, it means that a new cookie is created for every different domain that is visited in the same session and the old one gets discarded. What I needed was a single cookie that is persistent throughout the session, even when the domain changes. Hence, passing domain: "lvh.me"
solved the problem in development. This creates a single cookie that stays there between different subdomains.
For anyone needing further explanation, this is a great link: http://excid3.com/blog/sharing-a-devise-user-session-across-subdomains-with-rails-3/
Solution 2:
http://excid3.com/blog/sharing-a-devise-user-session-across-subdomains-with-rails-3/
"The part you want to watch out for here is that if you set :domain => :all like is recommend in some places, it simply won’t work unless you’re using localhost. :all defaults to a TLD length of 1, which means if you’re testing with Pow (myapp.dev) it won’t work either because that is a TLD of length 2."
In other words you need:
App.config.session_store ... , :domain => :all, :tld_length => 2
Also a good idea to clear your cookies
Solution 3:
I was looking for a way to solve this problem without having to explicitly state the domain name, so I could hop between localhost, lvh.me, and whichever domains I would use in production without having to keep editing the session_store.rb file. However, setting "domain: :all" didn't seem to be working for me.
Ultimately I found that I needed to state the tld_length (top level domain length) in that expression. The default tld_length is 1 while example.lvh.me has a tld_length of 2 and 127.0.0.1.xip.io has a tld_length of 5, for example. So what I had in the session_store.rb file for subdomains on lvh.me in development and whatever else in production was the below.
MyApp::Application.config.session_store :cookie_store, key: '_MyApp_session', domain: :all, tld_length: 2
Hope this helps someone, as it took me a long time to find this answer!
Solution 4:
For some reason replacing :all
with the domain did not work (rails 3.2.11) for me. It took a piece of custom Middleware to fix it. A summary of that solution is below.
tl;dr: You need to write a custom Rack Middleware. You need add it into your conifg/environments/[production|development].rb
. 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.com}
You can see that there will be separate entries for sub1.yourdomain.com
and othersub.yourdomain.com
and yourdomain.com
The challenge is to use the same session store file across all subdomains.
Step 1: Add Custom Middleware Class
This is where Rack Middleware comes in. Some relevant rack & rails resources:
- Railscasts about Rack
- Railsguide for Rack
- Rack documentation for sesssions abstractly and for cookie sessions
Here is a custom class that you should add in the lib
This was written by @Nader and you all should thank him
# 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
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.com"
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.com"
Note that the preceding dot is necessary. See "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 5:
I came across this while looking for the simplest way to set the cookie to be the root domain. It seems there is some misinformation about the :all
option when passed as the domain option. For most domains, it will actually work as expected, setting the cookie to the root domain (e.g. .example.com
for test.example.com
). I think most people experienced issues since they're using the domain lvh.me
to test. The regex used by rails to find a top level domain is defined to be DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
. If you note the last part, you can see that rails interprets lvh.me
as a TLD similar to com.au
. If your use case needs lvh.me
to work, then the :all
option won't work properly, however, it appears to be the simplest and best option for most domains.
TL;DR, the correct answer here, assuming you aren't developing on a 3 letter domain (or any domain that confuses the above regex) is to use :all
.