简体   繁体   中英

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?

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()

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)

Update: For Rails 5.x and lower, I found the rails_same_site_cookie gem to be a good option for adding SameSite=None; to all your app's cookies. It uses middleware to do it.

The secure_headers gem lets you configure the cookie policy out of the box:

SecureHeaders::Configuration.default do |config|
  config.cookies = {
    secure: true, # mark all cookies as "Secure"
    httponly: true, # mark all cookies as "HttpOnly"
    samesite: {
      none: true # mark all cookies as SameSite=lax
    }
  }

I used this solution to add SameSite=None to our cookies on a Rails 5 application.

Once a cookie is set you cannot modify the cookie properties like expiry , domain , path .

Browsers return only the cookie name and value once a cookie has already been set, over-riding any of the cookie property will create a new cookie. I would recommend to delete the existing cookie and create a new cookie with same name and value.

headers['Set-Cookie'] instructs the browser to create a new cookie and modifying the value in middleware gives you a very little control on the attribute value.

I have answered here how this can be achieved by modifying the Rack::Utils.set_cookie_header! method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM