Migrating the domain of a Ruby on Rails session cookie
Relevant links:
- MDN: HTTP Cookies
- MDN: Set-Cookie Header
- How Rails sessions work
- 3 things about cookies you may not know
Context
By default, Rails uses a cookie to track user sessions. You can configure what
it’s called and stuff in session_store.rb
, which might look like this:
GreatWebsite::Application.config.session_store :cookie_store, key: "great_session"
Unless you set a domain argument in that file explicitly then the session cookies will be created as Host-Only
, which means that the cookie’s domain will be set to the exact (canonical) name of the request host and their HttpOnly
flag will be set to true.
As an example, imagine you’ve got a great website at www.mygreatwebsite.com and you’re super pleased with it. People come, people use it, and they all get cute little great_session
cookies that look like this:
Name: great_session
Value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Domain: www.mygreatwebsite.com
HttpOnly: true
Expires: Session
Path: /
But then one day you decide you’d like to use a subdomain of your great website to host a dope software blog with dope articles about the dope work your dope engineers do: dope-engineering.mygreatwebsite.com.
Now, in your current configuration session cookies are specifically set to belong to www.mygreatwebsite.com, and as a result the dope-engineering subdomain can’t access their cookie. What a pickle!
Not to worry, you think, I’ll just make the session cookies available on all subdomains:
GreatWebsite::Application.config.session_store :cookie_store,
key: "great_session",
domain: :all
Now the session cookies will be set with domain: .mygreatwebsite.com
and they’ll be available from all subdomains as a result. Great:
Name: great_session
Value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Domain: .mygreatwebsite.com
HttpOnly: false
Expires: Session
Path: /
Problem
A cookie can only be overwritten (or deleted) by a subsequent cookie exactly matching the name, path and domain of the original cookie. Even though a cookie with domain “.example.org” set by www.example.org is perfectly valid, it will not overwrite a previous cookie of the same name which was set against “www.example.org”. - sitepoint
When Rails starts reading and setting session cookies with the new domain it does not replace a user’s existing session cookie - just create a new one that lives alongside it. What all of this means that your users now have two cookies with the same name but different domains, both accessible from www.mygreatwebsite.com.
Having two cookies with the same name isn’t necessarily problematic, but more often than not is. It’s pretty common to run into problems with logout for example as you can’t overwrite (and therefore delete) cookies across domains.
Solution
Change the session cookie’s name when you change it’s domain. It might ruin your super-neat naming convention for cookies, but it’s the fastest, simplest way to fix this problem without causing a cookie conflict.
GreatWebsite::Application.config.session_store :cookie_store,
key: "great_session_new",
domain: :all
For bonus points, delete the old session cookie as well. Note that this also wouldn’t work without having chosen a new name for our cookie, as browser’s aren’t designed to consistently handle multiple Set-Cookie headers for the same cookie name:
before_filter :delete_old_session
def delete_old_cookie
cookies.delete("great_session")
end