Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding 'SameSite=None;' cookies to Rails via Rack middleware?

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?

like image 635
Kelsey Hannan Avatar asked Jan 23 '20 00:01

Kelsey Hannan


People also ask

How do I set SameSite none cookies?

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.

How do I allow SameSite by default cookies?

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.

Should I use SameSite lax or strict?

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.

How do I fix my SameSite attribute?

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.


3 Answers

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)
like image 92
Carson Reinke Avatar answered Oct 19 '22 10:10

Carson Reinke


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)
like image 21
cesartalves Avatar answered Oct 19 '22 09:10

cesartalves


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()
like image 4
osowskit Avatar answered Oct 19 '22 10:10

osowskit