To make SameSite=None
cookies work in a rails application (or any rack-based ruby app)
you need to also FILTER OUT any clients that don't work nicely with this new flag,
namely clients who mistreat the None
value or drop the cookie when incompatible values
are seen.
-
-
Save bnorton/7dee72023787f367c48b3f5c2d71540f to your computer and use it in GitHub Desktop.
class ApplicationControler < ActionController::Base | |
... | |
def set_third_party_cookie(name, value, expires_in: 1.year) | |
cookies.encrypted[name] = { :value => value.to_s, :expires_in => expires_in, :domain => :all, :same_site => SameSite.value(request.headers['User-Agent']), :secure => true, :httponly => true }.compact | |
end | |
... | |
end |
... | |
## | |
# Until this commit is merged and released by rack | |
# | |
gem 'rack', git: 'https://github.com/rack/rack.git', ref: 'c859bbf7b53cb59df1837612a8c330dfb4147392' | |
... |
class SameSite | |
def self.value(agent) | |
send_same_site_none?(agent) ? :none : nil | |
end | |
private | |
## | |
# Based on https://www.chromium.org/updates/same-site/incompatible-clients | |
# | |
# Don’t send `SameSite=None` to known incompatible clients. | |
# | |
def self.send_same_site_none?(useragent) | |
return true if useragent.blank? | |
!same_site_none_incompatible?(useragent) | |
end | |
# Classes of browsers known to be incompatible. | |
# | |
def self.same_site_none_incompatible?(useragent) | |
web_kit_same_site_bug?(useragent) || | |
drops_unrecognized_cookies?(useragent) | |
end | |
def self.web_kit_same_site_bug?(useragent) | |
ios_version?(useragent, major: 12) || | |
(macosx_version?(useragent, major: 10, minor: 14) && | |
(safari?(useragent) || mac_embedded_browser?(useragent))) | |
end | |
def self.drops_unrecognized_cookies?(useragent) | |
return !uc_browser_version_at_least?(useragent, major: 12, minor: 13, build: 2) if uc_browser?(useragent) | |
chromium_based?(useragent) && | |
chromium_version_at_least?(useragent, major: 51) && | |
!chromium_version_at_least?(useragent, major: 67) | |
end | |
def self.ios_version?(useragent, major: nil) | |
/\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\// =~ useragent | |
$1 == major.to_s | |
end | |
def self.macosx_version?(useragent, major:nil, minor:nil) | |
/\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit\// =~ useragent | |
$1 == major.to_s && $2 == minor.to_s | |
end | |
def self.safari?(useragent) | |
/Version\/.* Safari\// === useragent && !chromium_based?(useragent) | |
end | |
def self.mac_embedded_browser?(useragent) | |
/^(Mozilla\/[.\d]+ \(Macintosh;.*Mac OS X [_\d]+\) )|AppleWebKit\/[.\d]+ \(KHTML, like Gecko\)$/ === useragent | |
end | |
def self.chromium_based?(useragent) | |
/Chrom(e|ium)/ === useragent | |
end | |
def self.chromium_version_at_least?(useragent, major: nil) | |
/Chrom[^ \/]+\/(\d+)[.\d]* / =~ useragent | |
$1.to_i >= major | |
end | |
def self.uc_browser?(useragent) | |
/UCBrowser\// =~ useragent | |
end | |
def self.uc_browser_version_at_least?(useragent, major: nil, minor: nil, build: nil) | |
/UCBrowser\/(\d+)\.(\d+)\.(\d+)[.\d]* / =~ useragent | |
major_version = $1.to_i | |
minor_version = $2.to_i | |
if major_version != major | |
return major_version > major | |
elsif minor_version != minor | |
return minor_version > minor | |
end | |
$3.to_i >= build | |
end | |
end |
I agree, the pipe should not be there. But there should also only be 1 space between the fist part and the second, so one of the spaces needs to be removed as well. I've tested with the 547 most common browser from this site and tried to manually verify all the incompatible cases.
I use this version (first parenthesis are not required, but makes it clearer)
/^(Mozilla\/[.\d]+ \(Macintosh;.*Mac OS X [_\d]+\)) AppleWebKit\/[.\d]+ \(KHTML, like Gecko\)$/ === useragent
These should all match, they End with (KHTML, like Gecko)
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko)"
https://developers.whatismybrowser.com/useragents/parse/1273795-webkit-based-browser-macos-webkit
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko)"
https://developers.whatismybrowser.com/useragents/parse/1253253-webkit-based-browser-macos-webkit
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko)"
https://developers.whatismybrowser.com/useragents/parse/1095784-webkit-based-browser-macos-webkit
This should not match, there's additional stuff at the end
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
https://developers.whatismybrowser.com/useragents/parse/1302439-chrome-macos-blink"
This is why you use real user agent parser.
In your
) based on the original incompatible clients code.
mac_embedded_browser?
method there's a pipe character (|
) that should be a space (