Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SameSite to Cookies #28297

Conversation

cfabianski
Copy link
Contributor

@cfabianski cfabianski commented Mar 5, 2017

Summary

Add SameSite to the cookies to reinforce the security.
Define Lax as a first step.
Strict is also supported but is too strict as its name suggests 😸

Other Information

SameSite is the new recommended way to keep your website secure. It would make CSRF even more secure.

Some links:

@pixeltrix
Copy link
Contributor

@cfabianski is there any risk of breaking an app when upgrading from an older version if we now enable SameSite by default. Also I don't see a test for making sure that it can be disabled.

Generally though 👍

@cfabianski
Copy link
Contributor Author

We could have a global setting to make it backward compatible. Though, I thought Lax would be safe enough to start with.

@cfabianski cfabianski force-pushed the improve-security-with-cookie-same-site branch from 5fa9239 to c950717 Compare March 7, 2017 00:23
Copy link

@AndyStabler AndyStabler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, just noticed a teeny tiny typo

Copy link

@AndyStabler AndyStabler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@cfabianski
Copy link
Contributor Author

@pixeltrix does it look better now?

@cfabianski
Copy link
Contributor Author

@maclover7 Do you have any thoughts about it?

Copy link
Contributor

@maclover7 maclover7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming along very nicely. Two quick things:

  • Can you also add an entry to Action Pack's CHANGELOG.md about this?
  • Can you add a test for what happens to the cookies when the config value is :no_protection?

@@ -534,6 +543,10 @@ def digest
def key_generator
request.key_generator
end

def same_site_protection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably just missing it, but where is this method used? I didn't see any calls in the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not but I followed the pattern which is to have access to this as part of the cookie. Want me to remove it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're not calling it anywhere directly, then I'd say to not add the method right now. If it's needed in the future, it's easy to add :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we go :)

@cfabianski
Copy link
Contributor Author

@maclover7 Done

Copy link
Contributor

@maclover7 maclover7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 looks good to me, but Security Team folks will probably want to review this 😬

@cfabianski
Copy link
Contributor Author

@maclover7 great thanks.
Do I need to contact the security team or will they just review it when available?

@maclover7
Copy link
Contributor

@cfabianski They'll review it when they get the chance

@cfabianski
Copy link
Contributor Author

@maclover7 do you have any update on that one?
I can resolve the conflicts but it would be nice to get an idea on whether or not this is something we might want.

@swrobel
Copy link
Contributor

swrobel commented Feb 7, 2018

Maybe a good feature for Rails 6?

@cfabianski
Copy link
Contributor Author

@swrobel I hope so! 😺

@cfabianski
Copy link
Contributor Author

@swrobel fixed the conflicts just in case somebody wants to make it part of Rails 6 :) /cc @maclover7

@cfabianski
Copy link
Contributor Author

I'll have a look at the failures and I'll get them fixed in the meantime

Copy link
Member

@gmcgibbon gmcgibbon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameSite is still absent in Rails 6 (2d3a28b) cookie headers. I think it should be added! 👍 If you're still interested in getting this merged, please rebase and squash your commits.

@@ -1,5 +1,15 @@
## Rails 6.0.0.alpha (Unreleased) ##

* Add SameSite to cookies.

With this change in place, the CSRF protection will become useless.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should note that not all browsers support this feature. Also, since this is just text added to a header that browsers magically obey would it really ever deprecate authenticity tokens?

@@ -364,6 +369,10 @@ def handle_options(options) # :nodoc:

options[:path] ||= "/"

if %i[lax strict].include?(request.cookies_same_site_protection)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think %i[lax strict] should be moved to a frozen constant.


# Specify the SameSite level protection for the cookies
# Valid options are :no_protection, :lax, and :strict.
Rails.application.config.action_dispatch.cookies_same_site_protection = :no_protection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be grouped with config/initializers/cookies_serializer.rb as just a cookies.rb initializer?

@cfabianski cfabianski force-pushed the improve-security-with-cookie-same-site branch from c740c8c to 4fa8b05 Compare August 19, 2018 19:33
@cfabianski
Copy link
Contributor Author

Hey @gmcgibbon, thank you for your review. I managed to implement the changes you asked for.
I've also squashed the commits. Let me know if there is anything I can do :)

Copy link
Member

@gmcgibbon gmcgibbon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Code and tests look great but I'm still a little hesitant on the changelog entry.

@@ -1,3 +1,13 @@
* Add SameSite to cookies.

With this change in place, the CSRF protection will become useless.
Copy link
Member

@gmcgibbon gmcgibbon Aug 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the IETF doc, this isn't true:

   "SameSite" cookies offer a robust defense against CSRF attack when
   deployed in strict mode, and when supported by the client.  It is,
   however, prudent to ensure that this designation is not the extent of
   a site's defense against CSRF, as same-site navigations and
   submissions can certainly be executed in conjunction with other
   attack vectors such as cross-site scripting.

   Developers are strongly encouraged to deploy the usual server-side
   defenses (CSRF tokens, ensuring that "safe" HTTP methods are
   idempotent, etc) to mitigate the risk more fully.

You should re-word to describe SameSite as a CSRF protection additive as opposed to a replacement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed the update here sorry. I've updated it as well now (squashed)

@cfabianski cfabianski force-pushed the improve-security-with-cookie-same-site branch from 4fa8b05 to cff096e Compare August 20, 2018 07:07
Copy link
Member

@gmcgibbon gmcgibbon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last comment regarding the changelog and then this should be good to go! 😄

For now, the default is `:no_protection` which makes it backward compatible.
By switching from `:no_protection` to `:lax`
in `config/initializers/cookies`, the cookies will get
more secure than ever.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this sentence, I would say

By switching from `:no_protection` to `:lax` in `config/initializers/cookies.rb`,
cookies won't be sent by browsers in cross-site POST requests.
`:strict` will disable cookies from being sent in both cross-site GET and POST requests.

to provide a little more detail on this feature.

@cfabianski cfabianski force-pushed the improve-security-with-cookie-same-site branch from cff096e to beac956 Compare August 20, 2018 15:37
@cfabianski
Copy link
Contributor Author

@gmcgibbon Thank you. I've updated it!

Copy link
Member

@gmcgibbon gmcgibbon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@cfabianski
Copy link
Contributor Author

@gmcgibbon let's hope we can get it merged now :)

@KelseyDH
Copy link

KelseyDH commented Jan 23, 2020

@KelseyDH This is similar to what we had to do. We also had to evaluate whether or not to actually mutate the cookie based on the useragent using the logic suggested by the Chromium team as it breaks old clients (as discussed above).

Thanks for sharing that! Fortunately it appears about 80% of clients are on SameSite compatible browsers.

As much as I hate to say it, this seems like something Rails might need to work around. Perhaps this change will get delayed, but my guess is that it will get through eventually...even Rails 6.1 will have to reckon with the choice of breaking some group of browsers or doing some useragent-specific responding.

Yeah Rails will probably need to begin intelligently parsing User-Agent headers for compatibility.

As for the actual cookie editing, we did a manual string edit using regexes.

@HarrisonB I would love to see this sample code if you have it. 🙏 After February 4 there are going to be a lot of Rails developers scrambling to find workarounds.

@hibachrach
Copy link

@KelseyDH unfortunately I cannot share the code at this time.

@lukad
Copy link

lukad commented Jan 23, 2020

@KelseyDH I can share our code. We're manipulating the Set-Cookie header string manually as well.

class SameSite
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)

    headers['Set-Cookie'] = headers['Set-Cookie'].split("\n").map do |cookie|
      next cookie if cookie.blank?
      next cookie if cookie =~ /;\sSameSite/

      cookie + '; SameSite=None' + '; Secure'
    end.join("\n")

    [status, headers, response]
  end
end

This is a stripped down version. In our production code we also check that we only set SameSite=None for routes specified in the middleware args.

Also note that we're making sure the cookie is Secure because that will be a requirement for this SameSite policy: https://www.chromestatus.com/feature/5633521622188032

@p8
Copy link
Member

p8 commented Jan 23, 2020

There's also: https://github.com/pschinis/rails_same_site_cookie
But I haven't used it yet.

@sudoremo
Copy link
Contributor

sudoremo commented Jan 23, 2020

Given this is set to break all Rails applications using Chrome (~80% of browser users) in 14 days... backporting this to Rails 5.x as a patch ASAP should be a high, high priority. Two years ago Google warned about these changes and it's disappointing Rails took this long to add this configuration to the cookies options hash.

@KelseyDH: You got me startled here for a second ;) But if I got the announcement right, Chrome 80 is not disposing all cookies without the SameSite attribute, but just treats them as SameSite=Lax automatically. And, additionally, it won't allow SameSite=None cookies anymore that are not flagged as Secure. So I think all Rails applications that do not rely on Cross-Site cookies should still work without any changes. Is that right?

@kaspth, on an unrelated matter: Sorry for not answering you back then, I was on vacation for a few weeks and was somehow under the impression that it hadn't been merged yet (may need to improve my GitHub skills there^^).

@walkersumida
Copy link

@p8 It works on Rails 5.

@vfonic
Copy link
Contributor

vfonic commented Jan 27, 2020

@rafaelfranca

It depends on the definition of warning. I got no email from Google telling us they would unilaterally make a change that would break a lot of things in the internet, even old versions of their own browser if some app, I don't know decide to send SameSite=None by default to every cookie.

I understand your point of view. What everyone here wants is a working internet. The fact that Google acts the way they do, doesn't mean we need to retaliate in the same way. We've been writing fixes for IE for years, not because we loved the browser, but because we wanted our users to be able to use our webapps.

Chrome is the most used browser in the world. It would be great that one of the most popular web frameworks had a full support for it.

@wlipa
Copy link

wlipa commented Feb 5, 2020

The release of this in Chrome has been pushed back a little. "The SameSite-by-default and SameSite=None-requires-Secure behaviors will begin rolling out to Chrome 80 Stable for an initial limited population starting the week of February 17, 2020, excluding the US President’s Day holiday on Monday."

But apps that need cookies to work in a cross-origin manner (like when embedded in an iframe) will need some fix soon.

https://www.chromium.org/updates/same-site

@szpasztor
Copy link

SameSite enforcements are being rolled out as of Mar 2. Any updates on this ticket? This is starting to cause breaks in some production apps.

@Fjan
Copy link
Contributor

Fjan commented Mar 4, 2020

It turns out that old Safari browsers are broken with SameSite:None if you rely on cross-site cookies. For the convenience of other people needing to fix this, this monkey patch wil set SameSite:None if it sees a new Chrome browser to get some immediate relieve. Just drop in config/initialisers, should work on Rails 5 & 6, but you do need the latest version of the rack gem. (Edit: added Firefox)

module ActionDispatch
  class Cookies
    class CookieJar #:nodoc:
      def handle_options(options) #:nodoc:

        # Monkey patch to get app to work on Chrome and Firefox work inside iframes again
        if request.ssl? && request.user_agent =~ /Chrome\/8|Firefox/
          options[:same_site] ||= "None";options[:secure] = true
        end

        if options[:expires].respond_to?(:from_now)
          options[:expires] = options[:expires].from_now
        end

        options[:path] ||= "/"

        if options[:domain] == :all || options[:domain] == "all"
          # If there is a provided tld length then we use it otherwise default domain regexp.
          domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP

          # If host is not ip and matches domain regexp.
          # (ip confirms to domain regexp so we explicitly check for ip)
          options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
            ".#{$&}"
          end
        elsif options[:domain].is_a? Array
          # If host matches one of the supplied domains without a dot in front of it.
          options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
        end
      end
    end
  end
end

@yboulkaid
Copy link
Contributor

yboulkaid commented Mar 18, 2020

For anyone running into this, this is already possible today for session cookies:

Rails.application.config.session_store(:cookie_store, same_site: :none)

Note that if you want to use none as an option to same_site, you'll need to be on a rack version that's higher than 2.1.0, see rack/rack@c859bbf.

This is because rails' CookieStore extends Rack::Session::Abstract::Persisted, see

# Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
# options described there can be used to customize the session cookie that
# is generated. For example:
.

@Fjan
Copy link
Contributor

Fjan commented Mar 18, 2020

@yboulkaid That will set it on all browsers, so that's fine but only if you do need to worry about old Safari clients.

@blackwellsmith
Copy link

How do you implement SameSite attribute for cookies in rails? Ok so I have searched and searched. May explanations on what is happening with SameSite. I can't find anything that shows me how or where to do that. It's like I am completely missing something. Can someone share some insight on how to set this attribute in Rails? I need something concise not vague. Thanks!

@blackwellsmith
Copy link

ok so an update I have SameSite=None but it need to be secure as well any ideas on how todo that?

@yeonhoyoon
Copy link

@blackwellsmith you can add secure: true option.

@blackwellsmith
Copy link

I tried that it didn’t work. I am currently trying it with the rails same site cookie gem and I can see the cookie. I am still trouble shooting some issues.

@shawndeprey
Copy link

@Fjan Solution above worked beautifully as a short term patch given browsers currently sparsely support this config.

It would be nice to have this type of middleware easily configurable in the Rails base config somewhere; for API only applications this new cookie issue is becoming increasingly prevalent.

@Fjan
Copy link
Contributor

Fjan commented Aug 1, 2020

@shawndeprey Note that the latest Firefox also drops cookies inside iframes unless they are secure and same-site. It's safe to set this on older Firefox browsers, I changed the test in the monkey patch to:
if request.ssl? && request.user_agent =~ /Chrome\/8|Firefox/

@shawndeprey
Copy link

@Fjan that's good to know and thanks for the updated config. This is super helpful. 👏

@earthctzn
Copy link

earthctzn commented Aug 28, 2020

@yboulkaid I tried this on Rails 6.0.3.2 with Rack 2.2.3 and it had no effect on my _session_id cookie. I only got it to work using the https://github.com/pschinis/rails_same_site_cookie that @p8 mentioned above- thanks for that btw! Here is what I tried, what did I miss? Thanks for any responses.

In config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: '_MY_APP', same_site: :none, secure: true
and also tried.
Rails.application.config.session_store( :cookie_store, key: '_MY_APP', same_site: :none, secure: true)

For anyone running into this, this is already possible today for session cookies:

Rails.application.config.session_store(:cookie_store, same_site: :none)

Note that if you want to use none as an option to same_site, you'll need to be on a rack version that's higher than 2.1.0, see rack/rack@c859bbf.

This is because rails' CookieStore extends Rack::Session::Abstract::Persisted, see

# Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
# options described there can be used to customize the session cookie that
# is generated. For example:

.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet