Problem/Motivation
Drupal 7 does not set the samesite attribute for PHP session cookies, unless on PHP 7.3 or higher. Up until recently, all major browsers treated cookies without this attribute as if it were samesite=None. Recently (July 2020), Google Chrome has changed this with the release of Chrome 84, and cookies are treated as "Lax" if there is no samesite attribute set. The Drupal contrib module ecosystem has by and large been developed with the implicit assumption that browsers treat cookies as samesite=None. Additionally, some sites/modules aren't yet ready to upgrade to higher versions of PHP.
Drupal 7's documentation presently indicates that PHP 7.0 and higher is supported.
Steps to reproduce
I and other users have seen consequences of this for Drupal Commerce off-site payment redirects. This is where the user submits a form that POSTs to another domain (payment processor), and the payment processor sends the user back to the site with another POST (containing the verified payment data). Such an arrangement is a way of attaining PCI compliance (by the Drupal website never seeing a credit card number or other sensitive account information).
Contrib modules don't seem to be an appropriate/feasible place to change this in.
Proposed resolution
Modify drupal_session_start() so that it sets this attribute for PHP session cookies.
Since Drupal 7's contrib modules have been developed with the implicit assumption of samesite=None (and this is how web browsers have acted), explicitly set this cookie attribute. On PHP 7.3 and higher, this attribute can be based off of the php ini option: session.cookie_samesite="None". There is currently a related issue for Drupal 9.1.x, but it is for setting samesite=Lax.
Lower versions of PHP do not have built-in support for the samesite attribute. However, users on StackExchange and elsewhere have pointed out a workaround for PHP < 7.3 session cookies. The workaround involves modifying the cookie's path to append the string "; samesite=None" (or Lax, or Strict).
Detect incompatible browsers (which reject cookies having this attribute) via their User-Agent string, and avoid setting this attribute for them. Known incompatible clients are documented here: https://www.chromium.org/updates/same-site/incompatible-clients
Remaining tasks
Discuss and patch.
User interface changes
None
API changes
None. Possibly support a config variable that can be used as a substitute for the ini option (for PHP versions lower than 7.3).
Data model changes
None
Release notes snippet
Explicitly set the samesite cookie attribute (default: None).
| Comment | File | Size | Author |
|---|---|---|---|
| #149 | Screen Shot 2021-04-27 at 12.00.07 PM.png | 231.16 KB | thalemn |
| #144 | session-secure.png | 88.35 KB | znerol |
| #143 | Screen Shot 2021-04-22 at 8.32.23 AM.png | 114.19 KB | thalemn |
| #109 | 3170525-109.patch | 17.21 KB | mcdruid |
| #109 | interdiff-3170525-100-109.txt | 702 bytes | mcdruid |
Comments
Comment #2
cilefen commentedSymfony Framework emulates the behavior of PHP < 7.3.
Comment #3
nullkernel commentedI'm attaching a patch for drupal_session_start(). I figure even if this doesn't end up incorporated into a Drupal core release, it could help some people patch their sites to mitigate problems for their Chrome users.
I have this using a configuration variable I've called "legacy_php_samesite_cookie_attribute_value". Site administrators will be able to set this value if they want a different value than None assigned. If they choose to set it to an empty value (false, empty string, etc), the attribute won't be set at all (same behavior as Drupal 7 today).
Comment #4
nullkernel commentedI think the patch I provided doesn't go far enough. Setting the issue to to Needs Work for the following reasons:
Comment #5
nullkernel commentedI'm uploading another patch to test. This will make PHP 7.3 have a "None" samesite attribute if there isn't a preference set. It will also avoid adding a workaround for PHP < 7.3 if there is already a workaround in place (such as a ini setting for the path).
It doesn't however have anything for clients that are incompatible with samesite=None cookies. No interdiff provided because basically all the lines have changed.
Comment #6
akorkot commented@nullkernel,
We have a similar case on one of our web apps (Builded with D7), We have tested your patch and it works perfectly.
Hoping it will be accepted!
Thanks !
Comment #7
simonholt83 commentedThanks for the patch!
It was really helpful in getting us on the right track. We were experiencing some issues with a POST-redirect back to our CMS from a payment provider as the example given in the description.
However some clients is incompatible with SameSite=None as explained here: https://www.chromium.org/updates/same-site/incompatible-clients
To sum up there are two main issues:
1. All browsers on iOS 12 + Safari and Embedded browser MacOS 10.14: Will treat cookies marked with `SameSite=None` as if they were marked `SameSite=Strict`
2. Chrome 51 to Chrome 66 (inclusive on both ends) + UC browser on Android prior to version 12.13.2 will reject cookies with SameSite=None.
Looking at our current web-analytics it turns out that issue 1. would hit around 6-7% of our users.
We have therefore modified the patch from #5 adding some user agents sniffing to avoid setting SameSite=None in devices effected by issue 1.
I'm attaching patch here in case someone will find it useful.
All regex used in the patch are from the example code in https://www.chromium.org/updates/same-site/incompatible-clients
Comment #8
nullkernel commentedThanks for the feedback and patch. I agree that it's useful to avoid setting the attribute for incompatible browsers. Last week I further patched Drupal core and probably should have shared the patch sooner. I intended to share it after I'd found time to write simpletest test cases (and set up my dev environment for it), but I'm not sure when that will be.
I reviewed the patch in #7 and figured I'd share my changes since mine has a few improvements:
Since I'm not at the moment able to produce simpletest test cases, I'm providing a text file that has user agent strings. Each user agent string takes up one line. The first section is for user agents that are known to be incompatible (and should cause the function to return FALSE). The second section is for user agents that are not known to be incompatible (and should cause the function to return TRUE). Perhaps someone may find them useful and/or use them for test cases.
I've dropped the "(for PHP < 7.3)" part of the issue title because the incompatible browser detection logic is something that benefits newer versions of PHP.
Comment #9
simonholt83 commentedThanks for the updated patch!
This is certainly an improvement over our patch and we will use that instead.
Comment #10
simonholt83 commentedI've experimented a little with PHP >= 7.3 support.
Looks like we have to use the new option array signature for PHP's setcookie function to support SameSite. This is only supported in >= 7.3 so we will also have to check PHP version when setting cookie.
Attaching patch with my experiment that adds new drupal_set_session_cookie() function that checks PHP version.
I have checked for the case where cookie is not secure but SameSite=None. Not sure if this is the correct way to handle this. Perhaps it would be better to enfore secure if SameSite=None? Not even sure if we should handle this at all.
An alternative approach would be to use something like this library instead for setting session cookie:
https://github.com/ovunctukenmez/SameSiteCookieSetter/blob/master/SameSi...
It also includes the compatibility checks.
Also a minor correction in the patch: I've changed the code in drupal_session_can_send_samesite_cookie_attribute() to return TRUE if SameSite is something other than None. I believe we should still try to set SameSite in other cases since only None is the issue. Also changed to only check for None instead of the other attributes.
Comment #11
simonholt83 commentedAttaching new patch with minor correction: use 'expires' instead of 'expire' in cookie params array.
Comment #12
simonholt83 commentedComment #13
ayesh commentedI'd be opposed to the idea of browser sniffing as it is quite fragile to implement in real world, and the added complexity to Drupal core.
As of now, samesite is supported in ~92 browsers, and PHP 7.2 is security fixes-only mode. We would be adding too much code to core to only support a small minority of users.
It is possible to add SameSite support without any changes in Drupal. SameSite cookies will not completely prevent CSRF, and would only be an added feature that I don't is worth the hassle of maintaining bespoke code to add support for sites using PHP 7.2 or lower, and those who cannot bother to edit a PHP.ini setting. This is a setting that can be changed even in shared hosts.
Comment #14
nullkernel commentedWhat I recall seeing was around 5% of users on impacted user agents (possibly a little more). These are users who used to be able to log in, use the website's interactive features, etc. For a website that has thousands of users, it adds up. Of course, this percentage will vary from site-to-site, depending on the audience.
Generally I'm not a fan of browser sniffing, but I know of no other way to predict if a browser will reject a cookie.
Even if a "better" way forward is to tell users that their browser isn't supported, the exact same browser sniffing logic would be needed to determine who needs to see the warning.
This setting negatively impacts (a minority of) users. The motive isn't to enable administrators to be lazy.
For the record, PHP 7.2 or lower can also have samesite set by ini setting (using the workaround in session.cookie_path). i.e.
session.cookie_path = "/; SameSite=None"instead ofsession.cookie_path = /(default)There are currently no hooks for custom modules to alter the behavior of drupal_set_session_cookie(). If there were, I might have implemented the hook and called it a day (instead of creating this core patch).
If it is decided that Drupal core won't be changed, perhaps this patch can be re-worked into code that is intended to run in settings.php. It could modify the
session.cookie_path(orsession.cookie_samesite) setting as appropriate, and the fix would survive future releases of Drupal core (without site administrators/developers needing to re-patch each core update).Comment #15
axle_foley00 commentedI'm with @nullkernel on this one. We've been significantly impacted by this on our E-commerce site and are desperate for a solution that will resolve this. We have tried setting
session.cookie_samesitetoNonein oursettings.phpfile for PHP >= 7.3 and that helped some users but there are still others experiencing the issue and I'm hopeful that one of these patches will resolve the issue altogether for us. I'm going to try testing the last patch.Comment #16
nullkernel commented@axle_foley00 Sounds good.. FYI I haven't done any testing of the patches by @simonholt83 nor reviewed in depth. An interdiff wasn't provided so I'm not sure exactly what/why other functions were modified. I'm not sure if the testbot failures are cause for concern (they might be). I've been a bit busy lately to put more work into it.
In case you have problems adapting/fixing the latest patches in this issue thread, you may also want to try the last one I uploaded. The latest patch I'd provided (success on all testbot runs) works on PHP 7.2, was QA tested by me and my client and is in production for their website. I think it should run on higher versions of PHP too. It successfully detects the incompatible clients and avoids setting the SameSite cookie attribute for them.
Comment #17
axle_foley00 commentedI tested the last patch drupal-3170525-11-samesite_cookie_attribute_support_for_legacy_php_versions.patch by @simonholt83 and I see where the SameSite cookie is set to "None" as expected. I was able to go through with a test payment on our development e-commerce site and everything seemed okay. The only thing I noticed was that once I logged out, I got the following message displayed:
Comment #18
axle_foley00 commented@nullkernel I tried your patch in comment #8 and it applied properly and sets the SameSite cookie to "None" as expected (which is good), however, we still seem to be encountering users who are getting the Page not found or Access Denied after payment error in a related Drupal Commerce Issue # 3169405. And these are in recent versions of Google Chrome too with UA String "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36". Any other ideas?
Comment #19
nullkernel commentedAre you having just as many users with this problem, or just a few?
From what I've seen when I applied the patch, there were at first a few stragglers. These users had received their PHP session cookie before the patch was applied (so they did not have SameSite=None).
Another cause (for a small handful of users) is that they'd stay on the payment page for too long. This website automatically deletes sessions after ~15 minutes of inactivity (this site doesn't have a client-side pop up to warn the user and let them remain logged in when time is almost up).
If you're fairly sure that neither of the above use cases are what you're encountering, I'd recommend trying to reproduce the problem yourself. When you're on the payment page, you can open Chrome's developer tools and take a screenshot of the cookies table. Then, submit your payment. When the browser posts to the off-site payment processor and redirects to your site, you can compare the cookies table (for the Page not Found page) to the cookies table you took a screenshot of. Especially look closely at cookies that start with "SESS..." or "SSESS..." and their values.
Hope that helps!
Comment #20
axle_foley00 commentedHi @nullkernel,
We cleared the Drupal sessions table after we applied the patch and are still seeing users experiencing the issue. I don't think our users are taking too long on the payment page either.
The only way I've been able to replicate the issue on my computer is by not setting SameSite=None or by setting it to Lax. Once I have your patch applied or set
session.cookie_samesitein mysettings.phpthen it works as expected. So I'm really not sure what is happening on those users Mobile phones or Computers. The only other thing I'm wondering about is if those users having the issue in Chrome, have some sort of Ad Blocker or are in Incognito mode or something else that is blocking 3rd-party cookies.I'm really at a loss as to why so many users are still having the issue. 🤷🏽♂️😩
Comment #21
nullkernel commentedSorry to hear that @axle_foley00. Unfortunately, it's difficult (or impossible) to fix something that you can't reproduce.
I'm not sure what payment gateway/module you're using. For the site I fixed this for (uses commerce_cybersource_sasop), third party cookies are irrelevant for this problem. Prior to the fix (setting SameSite=None), the main cookie Drupal uses (SSESS...) for user sessions was not present when the user returned to the page. Because Drupal no longer considered the user logged in, Drupal Commerce considered the requested checkout URL to be an unauthorized request (and gave the user the Page not Found error).
I'm wondering if you were using my patch exclusively at the time these users had their problem. i.e. that you started with a clean session.inc file when you applied my patch. Because, later patches I didn't write/test (#10 and #11) modified other functions. For example, drupal_session_regenerate(). If you had leftover remnants, maybe they are somehow causing a change to the SameSite attribute on a subsequent page request (I'm just speculating here, I haven't tested any of it). It sounds like you checked to ensure that the SameSite cookie attribute was "None" when you first logged in, but perhaps the attribute was missing/changed at some point before the user got to the checkout payment step. Perhaps only a certain segment of users are affected, such as ones who are checking out for the first time and are creating a Drupal user account.
As mentioned previously, if/when you reproduce the problem, compare the cookies you start with vs the cookies you end with. Any discrepancies you see can provide clues. Another technique you can use (once you reproduce the problem) is to use your browser's developer tools to inspect the request headers your browser sends when posting to your payment processor, and the post back to Drupal (with the decision information). It would be under the Network tab, and you'd want to check the "Preserve Log" checkbox. Compare the requests that happen in a browser that doesn't have a problem (such as Firefox) vs what happens in the Chrome (or other) browser that fails.
Good luck!
Comment #22
thalemn commentedI appreciate this issue thread - but I am not sure I understand potential fixes.
My current site is D7 with PHP 7.3 and Commerce Kickstart.
We are starting to see users being redirected to 404 after completing their purchase (using third-party vendor to process credit card). It appears that the order is completed successfully and our order confirmation email is sent. But the user is now logged out of the site.
Since I am using PHP 7.3, I figured I'd test (locally with DevDesktop) updating the php.ini field with session.cookie_samesite = "None"
This setting prevented me from logging into the site via /user/login.
So I tried session.cookie_samesite = "Lax"
I could login and create an order, test the payment, but was redirected to 404.
In all test cases, I used the latest version of Chrome (86.0.4240.80), started with a clean browser, and deleted any user session data from the database.
Is it my understanding that this issue is related specifically to newer versions of Chrome on either Mac or PC? Are there other browsers affected specifically (when using PHP 7.3)?
Should I put the session.cookie_samesite line in the settings.php file rather than php.ini? If so, what should that line be exactly?
I may be wrong, but I would not need the patch solution since I'm using PHP 7.3. Please confirm. Thanks!
Comment #23
axle_foley00 commented@thalemn: I believe when you put it in your
php.ini, it should have worked. But you can also try putting the following in yoursettings.phpfile:ini_set('session.cookie_samesite', 'None');Or you can try the patch that @nullkernel created in comment #8.
Comment #24
znak commentedHi, all
I updated the patch from comment #8 and added a new variable where we could check custom value for SameSIte attribute and set this variable in your custom code.
Comment #25
nullkernel commentedHi @Znak, #8 already has a variable for the SameSite attribute value. With #24 there are now two variables, which is a bit confusing: "samesite_cookie_attribute_value" and "samesite_cookie_attribute_custom_value". They seem to be intended for the exact same thing.
Comment #26
axle_foley00 commented@nullkernel, I was using your patch exclusively when I made the comment #18 and #20.
I think I'm narrowing down why some users are still having this issue on my site though despite applying the patch by @nullkernel. I am using Drupal Commerce 7.x and am now noticing that if a user selects a product and adds it to their cart before they login, that when they do proceed to their cart and get prompted to login (and then they login) that somehow the SameSite cookie value gets overwritten from "None" to being empty. Any ideas why that would happen or where to fix that?
I think if I can figure that part out then it will resolve this issue for good.
Comment #27
simonholt83 commentedIn patch #11 (interdiff in #12) I was trying to use the new $options array in PHP's setcookie function to set SameSite, when using PHP 7.3+. But the tests still fails, so it needs some more work.
In pactch #8 we take an advantage of a bug in PHP's setcookie function, where you can add SameSite to the $path argument, but this bug should have been fixed in PHP 7.3. See this answer on stack for more info: https://stackoverflow.com/a/51128675.
So to support settingt SameSite in PHP 7.3+ looks like we need to use the $options array argument. And since this new argument isn't supported in versions lower than 7.3 we still need to use the bug in older versions.
An alternative to using user agent sniffing to handle incompatible clients, would be to set two cookies like explained here: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
Comment #28
simonholt83 commented@thalemn
You can use the new php.ini directive in PHP 7.3 to set samesite, but then i guess it would be enforced for all clients and you won't be able to handle incompatible clients. This might be an acceptable solution for you.
Comment #29
axle_foley00 commented@simonholt83, I tried your patch again (#11) and it solves my issue in comment #26. It seems what was also happening for me when I only used patch #8 was then when a user session is regenerated, it updated the Session cookie without SameSite="None" and so those users were still being affected until I applied your patch which takes care of cookies set during session regeneration.
As it relates to why tests might be failing for your patch, I think that its because the following warning is being generated by PHP:
Warning: setcookie(): Unrecognized key 'lifetime' found in the options array in drupal_set_session_cookie() (line 580 of /path/to/drupal-root/includes/session.inc).And that has to do with the fact that your
$paramsarray references a'lifetime'key, however, it seems that the key really should have been'expires'when used withsetcookie(). I think if you fix that in the patch, it should then pass the tests.Comment #30
mcdruid commentedReally appreciate all the thought and effort that's gone into this!
However, I share @Ayesh's concerns (#13) about putting browser-sniffing code into core's session handling.
As @cilefen pointed out (#2), Symfony emulates PHP's samesite functionality for older versions of PHP, but that doesn't include doing User Agent sniffing to try and tailor behaviour for browsers that lag behind on this functionality.
I think it would be good for D7 core to add the samesite cookie emulation for older PHP versions (at present D7 tries to support versions all the way back to PHP 5.3, although how much longer that's feasible remains to be seen - see: #3155190: [meta] Should Drupal 7 drop support for older PHP versions?).
As for catering for User Agents with poor or non-existent support for samesite though, I'd argue that ought to be handled by contrib. I would prefer to add a hook into D7 core which would allow contrib or custom code to alter core's behaviour when it sets session cookies, rather than add browser-sniffing code into core itself.
Having a contrib module handle the incompatible browsers would allow sites to decide if they need this functionality, and also allow that module to update the browser-sniffing code and release more frequently than core.
Setting to NW on that basis; I'd like to get this into the next core release (see #3179845: [meta] Priorities for 2020-12-02 bugfix release of Drupal 7.76 / 7.77) but in order to do so, I'd like to see the approach changed to emulating the basic functionality for older PHP and providing a hook to allow contrib/custom code to alter core's functionality.
Comment #31
mcdruid commentedHere's a quick first pass at a patch which adds a
drupal_setcookie()wrapper, from which we can calldrupal_alter().We'd want to add tests and API documentation for the new alter hook, but initially let's ensure that tests still pass in the various PHP versions.
I'm not certain about passing the cookie name within
$contextetc.. but that can all be tweaked.If this works, it should allow the samesite cookie attribute handling to be done from a contrib (/custom) code - including emulation for older PHP versions to the browser sniffing for uncooperative clients... unless I'm missing something?
I'd be happy to consider samesite emulation for older PHP versions within core, but would prefer to leave the browser sniffing to contrib.
No interdiff as this is a completely different approach.
Comment #32
mcdruid commentedThe approach in #31 needs a bit of a rethink; the earlier patches used
session_set_cookie_params()indrupal_session_start()and without that we won't be affecting session cookies in many cases when a user logs in.Apologies if I'm retreading the same ground you've already covered, but this obviously helps me review the changes we're making as I gain a better understanding.
I'll post another patch ASAP which will probably be a bit more like #8 / #11.
Comment #33
mcdruid commentedThis patch has a tidier implementation of
hook_cookie_alter()(should that be "hook_session_cookie_alter"? perhaps).The emulation of samesite for PHP < 7.3 is added in. It feels like there's some duplication and it may be possible to refactor.
I've added an implementation of the new hook to the user module using the
samesite_cookie_attribute_valuevariable for older versions of PHP.I think this is getting closer to what I had in mind:
We'd still want to:
I'd like to see if tests pass with this patch first (noting that tests passed for #31 although it didn't really do what was intended).
Comment #34
mcdruid commentedThis patch adds an entry to default.settings.php, some API docs, and a couple of tests.
The tests can be a little fragile as they're checking for the session cookie in a 'Set-Cookie' header but
\DrupalWebTestCase::drupalGetHeader()will return the first header that matches. If - for example - you're using XDEBUG and that's creating another Set-Cookie header, the assertion might fail.We could iterate through all matching headers checking them for the session cookie if this looks like it's going to be a problem.
With any luck the tests should account for PHP versions before and after the introduction of the samesite functionality in PHP 7.3
I also renamed the hook to
hook_session_cookie_alter()and the variable tosamesite_cookie_emulation_valueas it only comes into play when PHP won't handle the samesite attribute itself.Comment #35
mcdruid commentedAfter considerable fiddling around I've had to resort to doing an
ini_set()outside of any function in session_test.module because I don't think there's a hook that runs early enough to affect the session cookie settings in order to test them. I think this is okay to do in this very specific situation, and is simpler than other options I considered such as hijacking thesession_incvariable.We may still want to improve the assertions which check the session cookies, per #34 - as mentioned if there are other cookies involved the session cookie might not be the first thing that's returned by
$this->drupalGetHeader('Set-Cookie', TRUE).With any luck the tests should now work with the newer PHP versions that have native support for the SameSite attribute.
Comment #36
mcdruid commentedLast patch for now; this is just a tweak to the tests so that we set SameSite to
Strictbefore trying to change it toLaxviahook_session_cookie_alter().I'm thinking that Strict is the least likely setting that any host the tests are running on will already have set up, so it's the best one to check we can change settings to and from.
Comment #37
mcdruid commentedAdded to the test coverage so we simulate "None", "Lax" and "Strict" via php settings or the emulation thereof.
Plus test various settings transitions via
hook_session_cookie_alter(). I don't think we need to cover every possible combination, but this should ensure that we're not passing tests just because the system running the tests is set up a certain way.Comment #38
fabianx commentedIt might be that this is called really early in the bootstrap process and needs hook_boot() under certain circumstances.
Maybe just a note to make.
The samesite should be in an if () ...
to not set NULL keys.
Probably should be in bootstrap.inc and also generally the alter hook should be:
`hook_drupal_set_cookie_alter()`
Comment #39
mcdruid commentedFixes per Fabian's review in #38.
I've moved
drupal_setcookie()to common.inc as opposed to bootstrap.inc as I think the former is generally the home for "wrappers or custom implementations of PHP functions".If this passes tests, my only remaining reservation would be whether the new alter hook is always safe to call so early in the bootstrap (per #38 1.)
Is there anything we can do to reduce the risk of it causing problems? I'm not sure I understand exactly what Fabian meant by "needs hook_boot()" ?
No interdiff I'm afraid as the patch needed a reroll (due to changes in default.settings.php)
Comment #41
mcdruid commentedOops, it'd help to rename the implementation of the new alter hook too.
Comment #43
mcdruid commentedErm, that'd apply to both implementations of the new hook (missed the one in the test module).
Comment #44
rot3r1I'm sorry for drupal that cant fix this major bug that interrupt whole site functionality.
because chrome is most popular browser today and with this bug we cant sell product and create eCommerce website.
after this long time no valid and official solution...
Comment #45
mcdruid commented@Fabianx and I have discussed this. As mentioned in #39 it seems risky to introduce a new hook which is potentially called so early. One risk is that caches can become polluted with incorrect / incomplete information etc.. and core's existing tests are unlikely to reveal a problem like that.
In order to get this feature added, we should remove the new hook implementation (initially at least).
If sites want to customise the behaviour (e.g. using browser sniffing) that could happen from settings.php via the variable system /
$conf.Anyone desperate to see progress on this issue could create a new patch along those lines; I'll get to it eventually if nobody else does, but I have several other plates spinning at the moment.
Comment #46
mustanggb commentedI don't understand why you'd want different PHP version to behave differently, by default.
From what I can gather this has the effect of:
- PHP less than 7.3 defaults to 'None'
- PHP greater than or equal to 7.3 defaults to '', which behaves like 'Strict' (in Chrome at least)
Wouldn't we be better defaulting to the same thing, either:
- 'None', because it behaves the same as it's always has and sites that want to can override with $conf
- 'Strict', because it's "better" by default and sites using payment gateways or whatnot can override with $conf
Also should the $conf override apply to all cookies, or only session cookies.
I'm thinking if we go with 'None' as the default it's fine for all cookies, whereas if we go for 'Strict' as the default we should only add a $conf override for session cookies, and contrib modules can decide what they want to do for themselves.
I'm leaning towards default 'None' everywhere, what does anyone else think?
Comment #47
mustanggb commentedHere is an attempted patch that tries that then, i.e.
drupal_setcookie()Todo:
PHP_VERSION_IDto\PHP_VERSION_IDComment #49
mustanggb commentedDone:
PHP_VERSION_IDto\PHP_VERSION_IDTodo:
Comment #50
mcdruid commentedThanks for picking this up.
I'm attaching an interdiff between 3170525-43.patch and 3170525-49.patch although a lot of the change is removing tests which mostly looked at the alter hook which is also being removed.
I think the idea was that on newer versions of PHP the default value for samesite is handled by PHP and can be controlled through PHP ini settings.
It may well be simpler to just use one setting / value for all versions though.
If we're now going to say that if you want to alter that value (e.g. with browser sniffing) that'll have to happen via
$conf, I think that should probably be outlined in default.settings.phpI want to refresh my memory on this one a bit and have a proper look at the latest patch, but this is looking pretty good, thanks!
..and yes, we'll want tests to cover the revised functionality.
Comment #51
mcdruid commentedHere's a set of very basic tests to check that setting the
samesite_cookie_valuevariable to valid values is reflected in the attributes of the session cookie.I'd like to add to this so that we check what happens when e.g. newer PHP versions have a given ini value for
session.cookie_samesiteand we set the variable on top of that. I believe the variable overrides the PHP ini setting, but would be good to confirm that with tests. I think some of the previous session_test.module code can be recycled for that.We could also add a test where the variable is not set to check Drupal's default, which should be "None" per:
(Should that use a constant for the default?)
We could also add test coverage for the case where a samesite option is passed to
drupal_setcookie()which differs from the default / variable.Comment #52
mcdruid commentedI think these additional tests cover the other scenarios - they:
session.cookie-samesitesetting is not altered by Drupal unless thesamesite_cookie_valuevariable is set.samesite_cookie_valuevariable overrides any PHPsession.cookie-samesitesetting (in PHP 7.3 and later).The relevant tests should be skipped for PHP versions before 7.3
Do we need to test anything else?
Comment #53
mcdruid commentedNoticed a couple of stray copy-pasta comments in the tests which I'll remove.
Also,
testSameSiteCookieAttributeNoneDefault()may not work if the testing system has its ownsession.cookie-samesitesetting which differs from 'None'.Comment #54
mustanggb commentedDo we need to test the value after logout?
Comment #55
mcdruid commentedSo checking the
set-cookieheader when Drupal tells the browser to delete the (session) cookie? Hmm, I'm not sure whether we'd expect that to have the SameSite attribute or not - would need to check.It looks to me like the PHP setting (in PHP 7.3 and above) is not being overridden by the variable, which will also need to be looked at a bit more closely.
Comment #56
mcdruid commentedI think what the tests for PHP 7.3 and above reveal makes sense.
We're doing:
So if there's no explicit setting for SameSite in the PHP settings (the default in PHP is an empty value which "means that no SameSite cookie attribute will be set") then the variable - or Drupal's default of "None" - is used.
If PHP has been set up with an actual value for
session.cookie-samesitethat'll be used, and the variable / Drupal default are ignored.That's why
testSamesiteCookieOverrideLaxToStrict()is failing.If we're happy that the behaviour is as it should be, we'll just need to change that test so that it instead verifies the explicit PHP setting is not overridden, and document the way this works properly in default.settings.php
I think the behaviour seems okay, but one problem is that this issue was originally filed because of the inconsistencies in browser treatment of the SameSite attribute. If the idea was/is to be able to override the attribute via
$conf(e.g. some code in - or an include from - settings.php that does browser sniffing) that's not going to work for PHP 7.3 and later if a PHP ini setting is in place.Any thoughts?
Comment #57
mustanggb commentedIf I understood correctly you're saying the behaviour is currently such that:
- Neither
$confnorsession.cookie-samesiteset, default toNone-
$confis set, butsession.cookie-samesiteisn't, use$confvalue-
$confisn't set, butsession.cookie-samesiteis, usesession.cookie-samesitevalue- Both
$confandsession.cookie-samesiteare set, usesession.cookie-samesitevalueI think the flexibility of being able to override the last combination is valuable, so should be changed to:
- Both
$confandsession.cookie-samesiteare set, use$confvalueYes I would expect it to have a value, the test could basically be:
- Anonymous user, is samesite set to
'None'- Login, is samesite still set to
'None'- Logout, is samesite still set to
'None'The reason I mention this, is because when I was moving a few things around in the code I came across the following situation:
- Anonymous user, samesite was set to
'None'- Login, samesite was still set to
'None'- Logout, samesite changed to
''So it's basically to ensure this doesn't occur.
Comment #58
mcdruid commentedI think this now has all the tests we've added so far passing (checked with local drupalci using PHP 7.4)
So need to verify that other PHP versions pass tests too, and that these tests really represent the functionality that we're after (e.g. compare them to #57 and think through all the permutations we want to handle).
Not yet added any testing of cookie attributes on logging out.
Comment #59
mcdruid commentedAdding a test for cookie attributes on logout.
Important question here is how would this change affect existing sites (e.g. assuming we put only an example
$confin default.settings.php)?I'm thinking sites would see a change in that their session cookies would suddenly get
SameSite=Nonefrom the default. If so, that may not be a desirable change. It would at least need a Change Record.@nullkernel, @simonholt83 and others that worked on this early on... does the current patch address your use case? The idea would be that any browser-snifffing code would need to be called from settings.php to adjust the
samesite_cookie_valuevariable in$conf.Comment #60
mustanggb commentedSo as I see it the possible scenarios for existing sites are:
- PHP pre-7.3, before patch has the value of
'', which behaves like'None'on most browsers and older versions of Chrome, but like'Strict'on newer version of Chrome, this is one of the behaviours we're attempting to resolve to ensure consistency across browsers, after the patch they will all behave like'None'by default.- PHP 7.3+ with
session.cookie-samesitenot set, basically the same as above.- PHP 7.3+ with
session.cookie-samesiteset,$confwill be commented out, so it'll use thesession.cookie-samesitevalue as expected.I actually think all that is fine and no-one should notice any difference, except that newer versions of Chrome now work as expected out of the box.
Comment #61
fabianx commentedThis logic needs to be moved to the call-site of the function, because else a caller might not get what they expect.
---
Overall the patch looks good, but we should talk about order of precedence for the samesite:
- 1. Explicitly given to the drupal_set_cookie function [IMHO if we add a new function it should be side-effect free]
- 2. Explicit $conf setting
- 3. PHP INI setting
- 4. None
is what I think makes the most sense.
Overall the logic should be something like:
Comment #62
finneCurrent patch #59 works well for me (PHP 5.6)
Comment #63
simonholt83 commentedThe patch has come a long way :) Thanks for the good work.
@mcdruid
An alter hook would have been easier, but I think we can make it work with browser sniffing in settings.php and the new patch.
Comment #64
mcdruid commentedI think this is pretty much what @Fabianx and I discussed (#61).
The new helper function is maybe not strictly necessary but hopefully makes the logic clear - the precedence is as outlined in #61 and spelled out in comments.
I've added a test for explicitly disabling the samesite attribute (by setting the variable to FALSE in
$conf).Tests are passing for me locally on PHP 7.2 but let's see what drupalci thinks with all the other PHP versions we support...
Comment #66
mcdruid commentedOops, yeah we'll want to remove the
samesite_inielement from$options.I added that so that we can differentiate between a param passed explicitly to
drupal_setcookie()and the PHP default which comes fromsession_get_cookie_params().Comment #67
simonholt83 commentedLooks good. We will set samesite variable FALSE for incompatible browsers then.
Another situation where the ability to disable samesite might be beneficial is local dev without https. If I remember correctly secure cookies are enforced with samesite.
Comment #68
mcdruid commentedThanks, I'm going to take that as RTBC :)
Comment #69
fabianx commentedIs this necessary?
Would this not lead in circumstances to setting a path twice and ending up with ';SameSite=;SameSite='
?
The rest looks good.
Comment #70
mcdruid commentedGood catch; I don't think that's necessary... checking that tests agree.
Comment #71
mcdruid commentedCouple of tweaks to make in comments - can be done on commit:
we want *to* simulate
remove reference to drupal_session_start()
Comment #73
mcdruid commentedOh, looks like we do need to process the cookie params in
drupal_session_start()if we want to be able to override them.In order to avoid duplicating code I'd prefer to reuse the helper function(s) there, but common.inc hasn't always been included that early.
So I've reluctantly moved the helpers into session.inc which is probably appropriate most of the time - possible exception being if
drupal_setcookie()is being used for something other than session cookies. The helpers could go intobootstrap.incinstead?I've added a simple check to try and avoid adding the SameSite hack to
$options['path']more than once. I suppose this doesn't cater for the case where we may have a different value for samesite than has already been added? Is that likely / possible?Tests pass again for me in PHP 7.2
Comment #75
simonholt83 commentedI think that could happen if the path already had samesite emulation from the ini and we try to override with different value from config.
In that case I think the correct is to use the existing samesite as the patch does now and ignore the new value from config.
Another concern: I see that we set "secure = FALSE" when setting insecure session cookie. That could give some issues with cookies being rejected by the browser if samesite=None.
Comment #76
mcdruid commentedWith this extra tweak to pass the result of session_get_cookie_params() to the helper(s) as samesite_ini I am still seeing a test fail for testSameSiteCookieAttributeNoneDefault with PHP 7.3
I think that may be because that test is explicitly setting session.cookie_samesite to an empty value via ini_set and that's disabling the SameSite attribute completely as opposed to allowing Drupal to set the default of None.
We need to work out if it's actually legitimate to set an empty value like that, and if so decide what the behaviour should then be. I'm not sure that removing the attribute is correct in this case.
Comment #78
mcdruid commented...waiting for that test to re-run, I think it'll fail though.
https://www.php.net/manual/en/session.configuration.php#ini.session.cook... says:
So it's legitimate to test for this php ini setting. However, testSameSiteCookieAttributeNoneDefault currently expects the Drupal default of "None" to be applied, which won't be the case with our stated order of precedence:
In this case, 3 is correctly being applied over 4.
So I think the test is now wrong as it is applied for PHP 7.3 and later.
This patch changes the tests so that we're checking "None" is the default, and separately checking that setting an empty value in php ini "means that no SameSite cookie attribute will be set".
Comment #80
mcdruid commentedtestSameSiteCookieAttributeNoneDefault is failing for PHP 7.3 because I think the default php setting is an empty value which means the attribute is omitted. IIUC that default is up to the distribution / packager of PHP though so it's probably not wise to rely on it being set a certain way.
So although it doesn't seem ideal, this patch explicitly sets session.cookie_samesite to None for PHP 7.3 and above for that default test.
Comment #81
mcdruid commented...and the patch.
Comment #82
mcdruid commented#73
#75
The emulation only happens for < PHP 7.3 and there is no ini for those versions, so I think we're okay to simply ensure we don't add the hack more than once.
Comment #83
mcdruid commented@simonholt83's other comment in #75:
See: https://web.dev/samesite-cookies-explained/#changes-to-the-default-behav...
...which links to: https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
So yes, it looks like browsers are moving in the direction of mandating that:
Hmm. Thanks for bringing that up.
I wonder if we should reconsider the default based on that; i.e. have the default be FALSE / no SameSite attribute is set?
What does D8/9 do?
Comment #84
mcdruid commentedConfirmed that with SameSite=None (being emulated and set as default with PHP 7.2) I am unable to login over plain http in a chromium-based browser because the cookie is not (and cannot be) marked as Secure:
The same problem happens in PHP 7.4 if we set
$conf['samesite_cookie_value'] = 'None';(or with session.cookie_samesite set to "None" in php.ini).We'll have to think about how to approach this.
Perhaps it's up to site owners not to set up impossible configs (e.g. plain http logins and SameSite=None), but it'd certainly be ungood to have a default that breaks session cookies (for the majority of current browsers) on non-https sites for older PHP versions.
Comment #85
mcdruid commentedThis changes the default to FALSE (i.e. no samesite attribute), which I think is the safest, least disruptive option.
Other than that, the order of precedence should be unchanged (see e.g. #78).
Most of the changes in the patch are to the tests and comments in default.settings.php
This is passing in PHP 7.2 and 7.4 for me, let's see if the bot agrees.
Comment #86
mustanggb commentedWhy? This surely this reintroduces the inconsistency that I mentioned previously!
i.e.
You're basically saying it should be broken out of the box, that doesn't make any sense.
It's always behaved like
'None'until Chrome broke things, why change it now?Comment #87
mcdruid commentedIIUC chromium-based browsers (perhaps somewhere between 2/3 and 3/4 of all browsers) have already implemented this behaviour:
https://web.dev/samesite-cookies-explained/#changes-to-the-default-behav...
FF has implemented the same behaviour as an experimental feature which is not yet on by default, but likely will be eventually.
If we default to
SameSite=NoneD7's session cookies will no longer be accepted by (chromium-based) browsers unless they also are also marked as Secure. So any site that does login over plain http (however inadvisable that is) will be broken; it will no longer be possible to login in the majority of browsers. I tested this in #84.There's an argument to say we should default to "Lax". See e.g. #3150614: Set SameSite on session cookies for D9.
However, I don't think we can justify defaulting to "None" if that would break login on what I am guessing would be a substantial number of sites.
Defaulting to FALSE / no attribute means no unexpected regression for sites when they update.
We are, however, providing the facility for sites to configure the attribute via either PHP ini or the $conf/variable system. Sites can conditionally change the attribute from settings.php if they need to.
Comment #88
mcdruid commentedCouple of small tweaks which could be fixed on commit if there are no further changes:
remove
samesite_inias this function won't return that elementsome copypasta/debug to be removed here (first and last lines)
message doesn't match the assertion (should say "Lax")
Comment #89
nullkernel commentedMy opinion is that those sites do not provide a justification for impacting other sites if drupal_is_https() can be used to detect this edge case. On a side note, I think that few production sites use insecure HTTP logins (it's hard to think of a good reason to, especially when free certificates can be obtained from Let's Encrypt).
I have yet to see one. I visited that issue and searched the thread for all occurrences of "Lax". There were a few developers who expressed that they like "Lax", but that's not technically an argument (bandwagon fallacy). In fact, the only "Lax" argument I saw in that thread was somewhat against it (by @berdir in that issue's #4). I prefer not breaking functionality for existing sites (but if the advantages outweigh the disadvantages, maybe a case could be made).
I'm not a fan of browser sniffing, but I'm less of a fan of inconsistent behavior across browsers. Although I originally went with the user-agent sniffing approach, in hindsight maybe this could have been approached with a mechanism that sets two cookies instead of one (like @simonholt83 suggested in #27). Basically, on the subsequent request, Drupal could detect if a SameSite=None session cookie was lost by the browser (by checking to see if the "legacy cookie" is present). If the "legacy cookie" is present, use it to regenerate a Set-Cookie header if needed (this time without SameSite=None), and unset the "legacy cookie" since it served its purpose.
Comment #90
mcdruid commentedSo is the suggestion that the default is something like this?
As for whether the default is Lax or None, the majority of browsers are now behaving as if it were Lax if there is no attribute. That in itself is an argument for Lax to reduce unexpected disruption. As you mention @nullkernel, we should...
Comment #91
mcdruid commentedTrying out handling the default like this:
That seems to work.
We'll need to add tests to cover both scenarios.
Would that address concerns about defaulting to no attribute (and leaving browsers to do their currently somewhat inconsistent thing) whilst avoiding breaking login on sites that don't enforce https?
Comment #92
mcdruid commentedFixes the small issues from #88.
The default for samesite is handled per #91, so 'None' for Secure cookies and FALSE / no attribute for cookies under plain http.
There's a new test for https / Secure cookies defaulting to 'None'.
The default only applies to PHP versions before 7.3 as any ini setting takes precedence (see #78) over the default, and PHP 7.3 and later always have an ini setting even if that is empty (/ no attribute) which is the default in most cases.
Comment #93
nullkernel commentedI'm curious as to how many (if any) of these are non-Chromium browsers (and if so, which ones they are).
I'm sure the Chromium team had discussions and gave some advance notice about this change, but it still caught people off-guard. The Chromium team surely knew it would break functionality for sites, and likely hoped that web developers would quickly make changes to adapt their applications to the new behavior.
I imagine the team felt this would ensure that developers for new web applications set the attribute explicitly, and thus avoid creating a vulnerability to unintended/malicious cross-domain requests. As for existing major web applications, I'd say they were by and large already hardened to guard against malicious cross-domain requests. For example, Drupal's Form API embeds tokens/build ids as hidden fields. This helps guard Drupal's forms against malicious cross-domain form submissions (since the attacker wouldn't know of a token value that's valid for the user).
That's another fallacy (appeal to authority) as opposed to a compelling argument. The Chromium team had whatever reasons they had. Other Chromium browsers inherited this behavior and their teams may or may not have had an opinion on this change. At least one major browser that I know of (non-Chromium - Firefox) has held off on changing this for now.
I disagree with your assertion that not setting "Lax" is tantamount to "breaking functionality". I created this issue once I became aware that functionality was broken due to the recent actions of the Chromium/Chrome team. There are over ten thousand Drupal 7 modules that have been developed over the years, and I see no basis to conclude that they expect "Lax" behavior by default. Nor do I see a basis to conclude that broken modules were changed (ahead of this in-progress Drupal Core issue) to depend on a "Lax" default.
Comment #94
mcdruid commentedWe can only speculate about the motivations of the Chromium team, and whether other browsers will inevitably follow suit per https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
There are a couple of issues I'd like to focus on in order to get this committed for the next release:
1) What should Drupal's default be for the samesite attribute?
#92 uses 'None' as long as the Secure attribute is also going to be set. The attribute will be omitted for non-secure cookies.
I am happy to stick with this; there is an argument to say it represents a change from current behaviour for the majority browser (family), but that's only been the case since summer 2020 when chromium started to default to 'Lax'.
2) With this order of precedence for the setting:
...the default is only ever applied for PHP versions without native support for
session.cookie_samesite(so PHP 7.2 and earlier).This is because the ini setting will be applied even if it's set as "no value" (meaning the attribute will be omitted). No value seems to be the PHP default in most cases AFAICS.
I think that's okay. The php ini setting can be changed as required, and if that's not possible the conf/variable is available.
Overall I'm happy with the functionality implemented in #92.
Anything else to be considered here or are we RTBC and ready for @Fabianx to review as Framework Manager?
Comment #95
mustanggb commentedI think so, thanks for listening.
My only comment for now is that this seems a little unclear:
4) The default of None or FALSE (no attribute) if the cookie is not SecureMaybe:
4) The default of None if the cookie is Secure, or FALSE (no attribute) if the cookie is not SecureOr even something like:
4) A value of None if the cookie is Secure5) The default of FALSE (no attribute), which will use inconsistent browser specific behaviours (not recommended)Hmm, I don't think this is true, I was seeing
''on PHP 7.4Comment #96
mcdruid commentedThanks for the feedback.
Yup, perhaps we could make the comment about the two alternatives for the default more clear.
I think that if you don't set the variable in PHP 7.3 or 7.4 you'll get whatever is set in php.ini (which is often no attribute because
session.cookie_samesiteis set to an empty value), and Drupal's default never comes into play.Will look closer at that if you're seeing something different.
Comment #97
fabianx commentedRTBC - with two changes:
Let's just move all three new helper functions to bootstrap.
- common.inc calling a session.inc function that has nothing to do with sessions was weird
- I think someone would want to call drupal_set_cookie without wanting to include common.inc
In the same vein we can remove the samesite_ini and just add another call to session_get_cookie_params().:
Move this to the _drupal_samesite_cookie function as:
at the same point.
Then we can also simplify the remaining case, before drupal_session_start():
- Just unset() the $options['samesite'] always and let the helper do the rest.
Comment #98
nullkernel commentedOn a site I manage, I noticed the following warning in Firefox (86):
This happens with the patch in #92 (as well as the fix I provided earlier in this issue). I haven't tested on a fresh Drupal install, but I assume this warning would also occur.
Possibly related issue: https://www.drupal.org/project/drupal/issues/3050444
Comment #99
ressaThanks for sharing that @nullkernel. #229825: backport "$_COOKIE['has_js'] must die" patch to 7.x is on the Drupal 7 to do list #3192080: [meta] Priorities for 2021-04-07 release of Drupal 7, perhaps a good reason to prioritize getting rid of the
has_jscookie altogether?Comment #100
mcdruid commentedTesting changes per review in #97
Comment #101
mcdruid commentedOops - interdiffs are not patches.
This will need a Change Record (/ Notice).
Comment #103
mcdruid commentedThe failing tests have reminded me why we did the dance with the
samesite_inielement in$options.Without doing that, we pick up
$options['samesite']from the calls tosession_get_cookie_params()in the session.inc functions (even although by default it's often set to an empty value).That being the case, we don't know whether this is a param that was explicitly passed to
drupal_setcookie()or whether it came from ini (meaningsession_get_cookie_params()).That means that the variable won't override the ini setting in the helper (we never get beyond 1 in the list):
So I think we need to go back to using
samesite_inito differentiate ini settings, allowing them to be 3 in the order of precedence.Comment #104
mcdruid commentedHere's a patch which moves all 3 functions to bootstrap, but leaves the samesite_ini implementation in place.
Interdiff is against #92 as not sure a comparison with #100 is useful.
Comment #105
nullkernel commented(Deleted this comment because I realized my confusion: drupal_setcookie() is a new function introduced by the patch, and the has_js cookie warning is not a symptom of changes made by this patch.)
Comment #106
nullkernel commentedChanging back to RTBC since the concern I raised in #98 is not something that was caused by this patch (and can be dealt with as a separate issue).
Comment #107
fabianx commented#100 would have worked, but it needs this line from the last patch:
to work correctly.
Comment #108
fabianx commentedAfter this change and if tests pass, this can go in.
Comment #109
mcdruid commentedWell spotted - with this small change, tests pass for me with PHP 7.4
...let's check everything else.
Comment #111
mcdruid commentedCommitted, but we still need a change record.
Comment #112
damienmckennaI've applied this patch to a local D7 site that was having problems with Chrome loosing the session during Paypal Payflow Pro checkouts. With the patch applied as-is, and no changes to settings.php, both Safari and Chrome can now use the site's functionality, including checking out with Payflow Pro (yay!).
However, if I set samesite_cookie_value to "None", as is recommended, it causes problems with Chrome - forms don't work and throw "The form has been modified" errors. The solution was to comment out $conf['samesite_cookie_value'] so it works as-is.
Comment #113
mcdruid commented@DamienMcKenna on the site(s) that had problems with the variable, what was:
On versions of PHP before 7.3 the difference between setting the variable to 'None' and not setting the variable would be that the default would be 'None' where the cookie will also have the Secure attribute (which should mean https), but it'd be empty if not (i.e. http / no Secure attribute). If you set the variable, Drupal will use that value unconditionally (same with later versions of PHP which have the
session.cookie_samesiteini setting).Could you maybe try this in settings.php?
That should yield the same result as not setting the variable at all, but if it makes the problem you encountered go away it will have helped us identify the cause.
Depending on what we find, this could be useful context for the CR. I may also tweak the comments in the bootstrap code and default.settings.php a little.
Comment #114
damienmckennaThat one site uses HTTPS only, $conf['https'] is set to TRUE, and it uses PHP 7.4.
Comment #115
mcdruid commentedI think for sites using PHP 7.3 and later I'd use the ini setting if possible, although as noted that'll be applied unconditionally. Hopefully not a problem if everything's always https.
I'm not sure what difference it would make here but the
$conf['https'] = TRUE;setting is quite misleading; that enables "mixed mode" whereby:https://www.drupal.org/https-information#drupal-config
You may well know all that already, but I think a lot of people misunderstand what this does (and it was removed in D8).
@DamienMcKenna I'd be happy to have a look at the misbehaving site if you want to share details in slack (although sounds like I'd need to login and submit forms etc..?) I'd like to understand what's going on if using the variable is causing a problem (especially if the ini setting does not).
Comment #116
damienmckennaOk, some further testing.
I set the variable to 'None' and removed the 'https' setting.
I then attempted three different checkouts:
If you have some time over the next week I'd appreciate it if you'd ping me on Slack and I can share credentials to the site. Thank you.
Comment #117
damienmckennaJust to follow-up here, it seems the problems I was seeing were because I still had $conf['https'] = TRUE; in my settings.php - I thought I'd removed it but I edited the wrong file by mistake.
Once that line was removed from the correct settings.php file the site's checkout appeared to work correctly, am just waiting for my client to confirm the fix.
Huge thanks to mcdruid for taking a half hour of his day to work through this with me.
Comment #118
mcdruid commentedAdded a draft CR.
Comment #119
mustanggb commentedChange record: https://www.drupal.org/node/3207213
Comment #120
nullkernel commentedIn case anyone here is interested, I published a contrib module ("SameSite Cookie") which provides a couple of capabilities:
Comment #121
cilefen commentedWe could use some reviews on the same for D9 #3150614: Set SameSite on session cookies. Thank you!
Comment #122
thalemn commentedWe had some issues with redirecting back to our site after a user makes a payment via third-party website.
We have updated D7 core to 7.79 and use PHP 7.4
I have added the following to the settings.php file:
I have installed the samesite_cookie module with the following settings:
SameSite attribute value to send: (no override)
Checked Enable legacy browser workaround
These settings appear to work with the following browsers (all latest versions) and OS:
Safari/Mac
Chrome/Mac and PC
Firefox/Mac and PC
Edge/PC
Can someone please confirm my settings are correct, or if I should change them.
Comment #123
mcdruid commented$conf['https'] = TRUE;...is poorly named - that's actually enabling "mixed mode" for https whereby Drupal will use a pair of session cookies only one of which will be for secure requests.
See: https://www.drupal.org/https-information#drupal-config
I don't think most sites want or need this.
I would try removing it if you can.
Comment #124
damienmckenna@thalemn: please see my the comments above - as @mcdruid says the 'https' setting is badly named, you should make sure to NOT set it to TRUE on a site as it breaks Chrome.
Comment #125
thalemn commentedThank you @mcdruid and @DamienMcKenna for your help.
Just to clarify, my site uses D7 Commerce and sends a user to a third-party site to process payment, then back to D7 site when payment is completed. After upgrading the site to Drupal 7.79, users would create an order using Chrome, go to third-party site to process payment, then come back to D7 site as logged out (not expected) and the order status set to pending (funds captured by third-party site), the expected result would be a completed order with the user logged in to see the order confirmation page. This created a terrible situation for our customers!
So I then realized that I need to do something with the configuration of the SameSite cookie attributes. I installed the samesite_cookie module and configured SameSite: NONE and enabled legacy browser fix. This did not fix the issue described above (user logged out and order status pending when using Chrome).
So I abandoned the samesite_cookie module and tried adding configuration only to the settings.php file. First I applied the $conf['samesite_cookie_value'] = 'None'; attribute and tested. This resulted in our pre-Core 7.79 update issue with users placing an order with Chrome, and after using the third-party site to process the payment, they returned to Drupal 404 page and were logged out (the order did process as completed, but the user did not get that message).
So that's when I added $conf['https'] = TRUE; to the settings.php file. And voila, everything appeared to work as expected. Created orders in Drupal Commerce, directed to third-party site for payment, redirected back to Drupal with a completed order page and still logged in.
But when I check the console logs, I am seeing messages that the SameSite cookie is not secure. I (perhaps foolishly) thought the https setting would correct this.
I guess I need help understanding how to configure my D7 commerce site now that I've updated the core to 7.79. Any further assistance is much appreciated.
Comment #126
nullkernel commentedHi @thalemn, I developed the samesite_cookie module. It is currently a "release candidate" because I wanted to wait for some feedback / bug reports (but I believe it should be ready to use for whoever wants to). If the module is enabled, any configuration for $conf['samesite_cookie_value'] in settings.php is overridden (i.e. you don't need to make any changes to settings.php as described in the Drupal 7.79 release notes for the module to work). If you do decide to use the module further and have problems / support requests, I recommend you open an issue in the module's issue queue (I assume most people in this thread haven't used it).
It sounds to me that you typically allow people to use your site with http or https (user's choice).
$conf['https'] = TRUEallows http and https browser sessions to be interchangable. So if someone logs on with http, and later tries to visit a https page (or vice versa), they are still logged in. This is generally considered less secure, and support for this use case was removed in Drupal 8.A cookie with a SameSite attribute is required to be flagged as "Secure", but cookies served over http cannot be marked as "Secure". I think this is why you're seeing the message in your browser's console logs.
The way I typically set up websites is to require https. I make it so that, anyone who tries to load a URL over http will receive a redirect to the https URL. I think you may want to do this for your site. I generally implement this restriction at another layer (such as Cloudflare, Apache, or a load balancer). This documentation goes into detail, includes some instructions, and could be helpful for you: https://www.drupal.org/https-information
Comment #127
thalemn commented@nullkernel thanks for the information. Much appreciated!
My site forces the user to use https, so there's never any none secure traffic on the site. That's why I'm confused about this SameSite cookie attribute. If I set it to NONE (either in your module or in the settings.php file), that should resolve the issue, right?
I haven't found a specific issue related to your module, just the SameSite cookie attributes in general.
Again, it appears to happen primarily with users who use Chrome (Mac or PC), and sometimes with Safari.
On my production site, I have reverted to using core 7.78 and when users complete an order (with Chrome) they are redirected to a 404 page. The order processes and is complete, but the user is logged out of the site and land on the 404 page.
I am doing all of my testing on my development site. Both prod and dev force https, so we can rule this out.
The issue is present when a user leaves the Drupal commerce site to process their payment on a third-party site, then return to the Drupal site when the payment is complete. When they return to Drupal (using Core 7.78), they are either logged out and presented with a 404 page (order is processed and complete), or (using Core 7.79) they are logged out and presented the home page (order is pending, not complete).
Comment #128
nullkernel commented@thalemn,
Maybe Drupal thinks the site is being served over HTTP when it is actually being served over HTTPS. This can, for example, happen with a reverse proxy. A way to know if this is happening is if you put in some test code that calls function drupal_is_https() and it returns false despite you actually loading a page with https. There's a comment on the "Using a load balancer or reverse proxy" page which might be helpful for you (setting
$_SERVER['HTTPS'] = 'on'). https://www.drupal.org/node/425990#comment-13681864Another diagnostic you can try is to look at the session cookie in Chrome's development tools (its name begins with "SSESS"). The one that begins with "SESS" is the http cookie. You might want to try after clearing your cookies (and commenting out the $conf['https'] line in your settings.php if you want to determine if you're getting a "SESS" cookie instead of "SSESS" when you're using https). Also, when looking at the row for the cookie(s), you can verify that the SameSite attribute value is what you'd expect it to be.
Comment #129
thalemn commented@nullkernel
Thanks for your continued help and advice.
I am now using your module for testing the SameSite cookie attributes (no attributes in settings.php). I have switched the values from (no override), None, Lax, and Strict. And after processing orders (using Chrome Dev tools Console with Issues tab and Include third-party cookie issues checked), I can see the SSESS cookie attributes changing. Cool. The SSESS cookie never is SESS.
So when I start with a clean Chrome browser session (Guest people account), I can successfully complete an order with all results as expected. There are a handful of cookies with the reply and complete requests (leaving the third-party site and going back to Drupal), that have undefined SameSite attributes (but are all secure). Only the NO_CACHE cookies are not secure and have no attribute. The one Facebook Pixel cookie is not secure and Lax.
So I think I will give this a go on production. We have an excellent CS team that will let me know if there are issues.
Thanks again for all of your help!
Comment #130
thalemn commented@nullkernel
Update this morning: I restarted my Macbook Air, confirmed that Chrome was the latest release, and did a test order.
After using the third-party site to process the payment, I was redirected back to Drupal. This time I was logged out and presented with the home page. The order is processed as pending, with no messages sent.
Using the Chrome dev tools, I can see that the SSESS cookie value is deleted (reply cookies), and that the SSESS cookie was secure and NONE. So what do you think is happening here? And if I place a second order, it will process as expected. But restarting my computer causes the issue again.
Comment #131
nullkernel commented@thalemn,
I'm wondering if the SSESS cookie was Secure and None on the payment page, or if you'd checked on an earlier page and somehow the cookie was regenerated on an intermediary page load (after you checking the cookie and before you arriving on the payment page). This can happen if one of the page responses include a Set-Cookie header with that SSESS cookie in the string (and without the SameSite=None in the string for that cookie).
If the SSESS cookie was present and SameSite=None on the payment page (last Drupal page before the off site payment redirect), it might be an issue that needs to be fixed by whatever Commerce payment module you're using. I assume that your return URL that you've configured with your payment processor is https, not http (and if not, change it to https).
Did this happen for you with my module enabled? If so, try disabling it and going with the Drupal 7.79 instructions for SameSite (and let me know if it makes a difference).
My module uses hook_boot(), since no actual hook was provided for this new drupal_setcookie() function. As far as I know, my implementation works - but there hasn't been extensive testing like there has for this Drupal Core change (which is why I have the module as "release candidate").
If it turns out that disabling my module and going with the Drupal 7.79 instructions solves the problem for you, I'd encourage you to create a bug report in my module's issue queue.
Comment #132
thalemn commented@nullkernel,
I disabled and uninstalled your module and set the cookie attribute via settings.php file: $conf['samesite_cookie_value'] = 'NONE';
Same results - SSESS cookie is deleted after submitting payment on third-party website and redirecting back to Drupal without completing the order (and I am logged out of Drupal and presented with the homepage). I am not familiar with how this works, but from what I can tell, when the third-party site 'replies', that's when the cookie is deleted.
So I think your module is working as expected since I get the same results either way.
I am starting to suspect that the third-party website is somehow causing the SSESS cookie to be deleted. What is baffling is that this issue only occurs the first time I place an order after rebooting my machine. If I place a second order, it processes as expected. (I use CCleaner to clean my machine before a reboot).
I appreciate your help.
Comment #133
damienmckenna@thalemn: Try using Chrome's web inspector to review the request when the initial form is submitted, check to see if you have two SetCookie headers, one with the "Secure" option and one without. I had this problem on a D7 site, every time the visitor used Chrome to do the checkout when they returned back the session had disappeared. It turned out to be the $conf['HTTPS'] option that caused the problem, removing that option fixed it and visitors using Chrome didn't have any problems.
Comment #134
thalemn commented@DamienMcKenna,
I'll check again, but I don't have $conf['HTTPS'] option in my settings.php file. Thanks for the suggestion.
OK, I see only one SSESS cookie in the request header (secure, None).
But in the response header, I see one SSESS cookie (secure, NONE), which has a value of deleted. I'm pretty sure this is causing the issue, but I don't know why. My settings.php file has $conf['samesite_cookie_value'] = 'NONE'; (using all caps for the attribute). The request header attribute is None (not all caps). Maybe the attribute is set as None somewhere else?
Thanks for you help!
Comment #135
thalemn commentedUpdate: I decided to try something related to roles and permissions. I have been testing the orders using Role A (minimal permissions). I decided to try Role B (administrator with maximum permissions) - the order processed and completed as expected. So I am thinking that we have a permission setting that is blocking/deleting the Session Cookie when returning to the website from the third-party payment site.
To test something, I restored the database from this morning's backup, then ran drush rr and drush cc all. Voila, every order processes as expected now. I didn't touch permissions, and using Role A (minimal permissions), the orders process and complete successfully. I have no idea why drush rr would fix the issues, but it did.
Comment #136
cwarts commentedHello,
i still have an problem with session cookies on D 7.79.
When i set Samesite Cookie to None as described i have no more errors when redirection from paypal as long as i´m a anonymous user.
When i fill my cart an whant to login in during checkout i get an error that my session id is a duplicate entry in db and my cart is gone. is there something i miss in my config?
only on chrome browser at the moment - every other browser is working normal.
thanks for your help!
Comment #137
thalemn commentedOur situation is very similar.
I added the following to the .htaccess file (in addition to the SameSite Cookie set to None as described):
Tested successfully on dev site. Now testing in production.
Comment #138
izmeez commented@thalemn and @cwarts Just out of curiosity, what version of php are you using?
Comment #139
thalemn commented7.4
Comment #140
cwarts commented7.3
Comment #141
znerol commentedOut of curiosity: do you operate your sites behind a reverse proxy? In this case Drupal might be unable to detect that the original request came in via HTTPS and thus the
Securecookie attribute is not added to the session cookie automatically.You can try to enforce the
Securecookie attribute by enabling the PHP ini settingsession.cookie_securefor all requests by adding the following line tosettings.php:Also instead of setting
$conf['samesite_cookie_value']I'd rather recommend setting thesession.cookie_samesiteattribute as PHP ini setting for consistency reasons (unless for sites on PHP<7.3 of course).Note that all of that is only working for sites which are HTTPS-only (HTTP to HTTPS redirect is okay though).
Comment #142
cwarts commented@znerol i´m using aegir for my site hosting so maybe there is also something to do.
i changed my php.ini settings as u explained in your comment before but the problem is the same.
when i try to login i got the error in log
and my cart is empty.
@thalemn did you find a way to get this working?
thank you.
Comment #143
thalemn commented@cwarts
Update: No we do not have a working solution. I thought the .htaccess file code I previously posted would fix the issue, but it didn't.
I think we should reopen this issue status to not fixed.
Comment #144
znerol commentedSetup can be complicated depending on the deployment and the sites requirements. I'll try to walk you through this.
Lax or None?
First step is to determine whether you need
SameSite=LaxorSameSide=None.SameSite=Lax. This option results in better protection for session cookies.SameSite=None. This is also the correct option if you have multiple providers and they use a mix of bothGETandPOSTreturn URLs.Easy: SameSite=Lax
Setup and testing are quite easy.
For PHP>=7.3
Add
ini_set('session.cookie_samesite', 'Lax');to yoursettings.phppreferably near the other session related settings.For PHP<7.3
Add
$conf['samesite_cookie_value'] = 'Lax';to yoursettings.php.The effects of this change can be tested easily even in environments lacking HTTPS support (which is most probably the case on a dev-setup). I do not know of any brower incompatibilities (please correct me if I'm wrong).
Hard: SameSite=None
First try to upgrade your payment process such that it is compatibly with
SameSite=Lax. This can be done with many payment providers by switching to Instant Payment Notifications (IPN). In that case, the payment service provider will communicate any changes of the payment status back to the site using a server-to-server request. In such a scenario it is not necessary anymore to include payment details in the return URL and thus that call can be downgraded to aGETrequest and thus will be compatible withSameSite=Lax.Setup and testing is a bit more complicated. This is because:
See: MDN.
This means that before trying to add
SameSiteattribute you need to ensure that the site is available via HTTPS and the site sets its session cookies with theSecureattribute. It follows that this cannot be tested anymore on the typical local dev-setup (unless you run HTTPS on the dev-machine of course).Pull up developer tools and inspect the session cookie of your site. If the
Secureattribute is not present, then stop here and fix that first. Make sure you do not enable mixed-mode.If everything is okay, then configure the
SameSitecookie attribute like this:For PHP>=7.3
Add
ini_set('session.cookie_samesite', 'None')to yoursettings.phppreferrably near the other session related settings.For PHP<7.3
Add
$conf['samesite_cookie_value'] = 'None';to yoursettings.php.If that works, then check whether your audience is using buggy clients. If that is the case, then you can try @nullkernel SameSite Cookie module which includes browser sniffing code. A working HTTPS setup and a properly flagged
Securesession is of course still required.SameSite=Strict why not?
For completeness, if you specify
SameSite=Strict, then every time somebody clicks on a link to your site, they will end up there without a session. Even if they've been logged in before (or when they had a cart before). Thus this option is really only useful for static sites which do not allow any end-user interaction.Still have issues?
If you have problems following this steps or if your site is behaving in unexpected ways, then please consider opening a support request. Also, in order to check whether the problem is site-specific or deployment-specific, consider deploying a fresh Drupal standard installation and try to reproduce the problem there.
Comment #145
GoddamnNoise commentedThanks a lot for the great explanation. I think it'll be very useful for anyone facing a similar issue.
Supose you have an e-commerce site with multiple payment methods, one redirects the user using a GET request after payment and the other redirects the user using a POST request.
Is there a solution in that case? Could the 2 payment methods work or it's only possible to make one of them to work?
Comment #146
nullkernel commented@GoddamnNoise,
You'd need to use SameSite=None for that case. It's less restrictive than Lax.
Comment #147
znerol commented> You'd need to use SameSite=None for that case. It's less restrictive than Lax.
Correct. I updated that. Also added a recommendation to try switching to a payment flow which does work with
SameSite=Laxfirst.Comment #148
cwarts commentedHello everybody,
on weekend i have time to try some configurations. i ended up with this two lines in my settings.php
i make several orders with anonymous and logged in user - all with paypal wps - and everything is working now.
i updated my live sites to this configuration and will have a look on it the next days.
thanks everybody for your help and hints on this.
Comment #149
thalemn commentedI've been doing some testing using BrowserStack.com and I have had issues using Edge on Windows 10.
My Drupal 7 site is using PHP 7.4, Core 7.79.
The screenshot shows that the Request Session cookie is SameSite: None, and secure.
After completing the order, the Response Session cookie is deleted (reply) (SameSite: None and secure)
Any ideas why this is happening? Let me know if you need more information about this issue.
Comment #150
thalemn commentedThe issue has been resolved by upgrading the simplesamlphp library to 1.19.0. This upgrade provides a means to set the SameSite cookie attribute to none (and secure). This appears to resolve our issues with a cookie being deleted after processing the payment and returning to the Drupal 7 Commerce site.
Comment #152
a-fro commentedThe recent conversation above is helpful. However, our setup is not related to payments, but rather our proxy system. We have a proxy server set up to serve some of the paths at our domain from a Drupal 7 instance, while serving other paths from a D9 instance. If a user is logged into D7, then visits a proxied D9 page, the session is removed and they can no longer edit the site. I tried adding
$conf['samesite_cookie_value'] = 'Lax';, but that didn't resolve the issue.Given that the session cookie gets clobbered when the user hits the D9 page, as opposed to when the user returns to a D7 path, it could be that there is something we need to do in D9 codebase to prevent it. Any suggestions would be greatly appreciated.