On February 4th 2020, Google Chrome will require SameSite=None;
to be added to all cross-site cookies. Rails 6.1 and soon Rails 6.0 have added a same_site: :none
option to the rails cookie hash:
cookies["foo"]= {
value: "bar",
expires: 1.year.from_now,
same_site: :none
}
But older Rails 5.x apps won't receive the upgrade to have access to the same_site
options hash. I know the SameSite=None;
cookie option can be manually added to Rails in a controller using:
response.headers["Set-Cookie"] = "my=cookie; path=/; expires=#{1.year.from_now}; SameSite=None;"
But my Rails 5.x app uses complicated cookie objects that modify cookies. Instead of breaking them apart, I would like to write Rack middleware to manually update all cookies with the SameSite=None;
attribute at once.
This StackOverflow answer shows a way to cookies can be modified to update cookies within Rack Middleware:
# lib/same_site_cookie_middleware
class SameSiteCookieMiddleware
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
# confusingly, response takes its args in a different order
# than rack requires them to be passed on
# I know it's because most likely you'll modify the body,
# and the defaults are fine for the others. But, it still bothers me.
response = Rack::Response.new body, status, headers
response.set_cookie("foo", {:value => "bar", :path => "/", :expires => 1.year.from_now, same_site: :none})
response.finish # finish writes out the response in the expected format.
end
end
# application.rb
require 'same_site_cookie_middleware'
config.middleware.insert_after(ActionDispatch::Cookies, SameSiteCookieMiddleware)
How do I re-write this Rack Middleware code to manually append SameSite=None;
into every existing cookie?
A New Model for Cookie Security and Transparency Developers must use a new cookie setting, SameSite=None , to designate cookies for cross-site access. When the SameSite=None attribute is present, an additional Secure attribute must be used so cross-site cookies can only be accessed over HTTPS connections.
Go to chrome://flags and enable (or set to "Default") both #same-site-by-default-cookies and #cookies-without-same-site-must-be-secure. Restart Chrome for the changes to take effect, if you made any changes.
The SameSite=Strict value will only allow first party cookies to be sent. This setting is good for user actions like login credentials, but the cookie will not be sent on the initial request to the webpage. The SameSite=Lax setting will allow the user to maintain a logged in status while arriving from an external link.
Resolve this issue by updating the attributes of the cookie: Specify SameSite=None and Secure if the cookie is intended to be set in cross-site contexts. Note that only cookies sent over HTTPS may use the Secure attribute.
I was able to get this to work with the following:
# frozen_string_literals: true
class SameSiteCookies
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
set_cookie_header = headers['Set-Cookie']
if set_cookie_header && !(set_cookie_header =~ /SameSite\=/)
headers['Set-Cookie'] << ';' if !(set_cookie_header =~ /;$/)
headers['Set-Cookie'] << ' SameSite=None'
headers['Set-Cookie'] << '; Secure' if env['rack.url_scheme'] == 'https';
end
[status, headers, body]
end
end
and adding to middleware with:
Rails.application.config.middleware.insert_before(ActionDispatch::Cookies, SameSiteCookies)
I had a problem with Rails 5 headers being frozen. This is similar to Carson's answer but it goes around this problem. Should work for both rails 5 < and Rails 5+.
# frozen_string_literals: true
class SameSiteCookies
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
set_cookie_header = headers['Set-Cookie']
if set_cookie_header && !(set_cookie_header =~ /SameSite\=/)
# the set cookie header variable is frozen
new_set_cookie_header = set_cookie_header.dup
new_set_cookie_header << ';' if !(set_cookie_header =~ /;$/)
new_set_cookie_header << ' SameSite=None'
new_set_cookie_header << '; Secure' if is_ssl?
headers['Set-Cookie'] = new_set_cookie_header
end
[status, headers, body]
end
private
def is_ssl?
# custom logic for my application
end
end
Insert the middleware
Rails.application.config.middleware.insert_before(ActionDispatch::Cookies, SameSiteCookies)
I was able to get all cookies to use SameSite=None
by default updating rack:
gem 'rack', '~> 2.1'
use Rack::Session::Cookie,
:httponly => true,
:same_site => :none,
:secure => true,
:secret => COOKIE_SECRET.to_s()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With