I was toying with the aspect of implementing SameSite
for my cookies today. I already had HttpOnly
and Secure
so I thought this probably wouldn't be a big deal.
Well, it turned out that lots of things broke once I implemented the setting. This occurred with both SameSite=Lax
and SameSite=Strict
. I did some research and found this was due to SSO being prone to breakage with SameSite settings of Lax
or Strict
(as opposed to None
):
My primary browser (Iron 70) is based on Chromium 70, so I never before encountered the change rolled out to Chrome 80 users back in February, which supposedly defaulted cookies without SameSite
values to Lax
. I installed the latest Google Chrome Portable to check it out and interestingly it seems the feature is currently not (thankfully) defaulting to SameSite=Lax
as it might have used to - my site only broke on there once I explicitly enabled the following header:
Header edit Set-Cookie ^(.*)$ $1;SameSite=Lax
It seems this is because without an explicit SameSite
, Chromium treats this is as "LAX + POST w/ 2-minute rule" by default (and I was testing rapidly, so it was within 2 minutes).
Even with Lax
, though, all my single-sign on is broken and my realtime chat doesn't work anymore - either using Websockets or XHR requests. When I try to do single-sign on, somehow I end up logged out of the main website, which also doesn't make much sense - basically, everything is messed up.
Is there any hope of getting XHR or Websockets to work again with Lax
? I have chat up at chat.example.com
, but I also allow access to it in a side panel on sub.someotherdomain.org
. My guess is the answer here is no, and the only way to get around it would make a URL available on the same domain which Apache simply points to the same script behind the scenes. Annoying, but it could be done - but is there another way?
My bigger question is: is single-sign on inherently incompatible with Lax
and Strict
? I've not really found much in the way of this. All the articles seem to treat breaking SSO with Lax
as inevitable, and there are even some diagrams that explain why it breaks, but does SSO have to be that way?
Most sites say to do SameSite=None
to get around this and force the old behavior in all user agents. Technically, this works, but I'm wondering if there's any way hope of ever being able to use Lax
instead? How could this be made to work without having to succumb to SameSite=None
?
TL;DR - Yes, you can use
SameSite=Lax
(but notSameSite=Strict
) and not break SSO!
There are two big things to note about SameSite
cookies:
POST
GET
Source: https://www.wst.space/cookies-samesite-secure-httponly/
Strict simply would not work, because it prevents any kind of cross-site request from sending cookies, which makes SSO impossible entirely. Strict
is not even a viable candidate.
That leaves us with Lax
and None
(which has been the default up until now, and is slowly being supplanted with Lax
).
- Is there any hope of getting XHR or Websockets to work again with
Lax
? I have chat up atchat.example.com
, but I also allow access to it in a side panel onsub.someotherdomain.org
. My guess is the answer here is no, and the only way to get around it would make a URL available on the same domain which Apache simply points to the same script behind the scenes. Annoying, but it could be done - but is there another way?
No.
The best solution here is to rewrite the URLs behind the scenes so you don't need to maintain duplicate resources. Either rewriting the URL using Apache's mod_rewrite
or simply doing an include('path/to/file.php')
would be an easy solution. The content returned is going to be exactly the same - but if it requires Lax
cookies to be sent, the browser must be sending them to a domain that is an ancestor of the current domain.
- My bigger question is: is single-sign on inherently incompatible with
Lax
andStrict
?
No, fortunately, not!
I've not really found much in the way of this. All the articles seem to treat breaking SSO with
Lax
as inevitable, and there are even some diagrams that explain why it breaks, but does SSO have to be that way?
It is true that a lot of SSO pages do break with SameSite=Lax
- but this failure is not inevitable - it's implementation-specific. Let's compare the original method with a revised method which is compatible with SameSite=Lax
cookies.
SameSite=None
)sub.example.org
- not currently logged in because no cookie is set on this siteexample.com
- if the user is not authenticated there, it redirects back and gives a username/password prompt. If the user is authenticated, there, continue.example.com
, read the user's session data and create a unique token for the SSO call. Dump the token into a database and POST back to the original site with the token that was inserted.sub.example.org
, read the token that was POSTED and query the database for that token, and from that retrieve the user ID.sub.example.org
- now the session works as expected, since $_SESSION['mysession']
returns the same information on both example.com
and sub.example.org
(because the user ID never changes, technically these are duplicate cookies).This will break with SameSite=Lax
. Why? Because the original request to the authenticator is using a POST
request - and this is to a foreign domain - and this is considered dangerous by both SameSite=Lax
and SameSite=Strict
- and cross-domain POST
s won't have cookies sent to the destination. Thus, the cookies aren't available and the authenticator doesn't know what user is authenticated so it can't create the temporary token for that user before posting back. That's why this doesn't work.
However, the important thing to note here is that the POST
request isn't sending any sensitive data (at least in the implementation described above). It's simply asking for authentication - it doesn't even have any sensitive data to send!
So, why are we POST
ing in the first place? Recall that SameSite=Lax
allows first-level GET
navigation (SameSite=Strict
does not). Thus, we can take advantage of this by simply using GET
instead of POST
for the initial redirect only.
How could this be made to work without having to succumb to
SameSite=None
?
Here's how. Because Lax
permits top-level GET
but not POST
(which is supposedly "dangerous"), use GET for the initial redirection instead of POST.
Paradoxically, GET
is arguably less secure than POST
, but the sensitive data (the token for user) is only sent on the final redirect back to the site requesting authentication - the initial redirect only says "Hey, I'm requesting authentication".
Here's a brief excerpt which backs up this possibility, which concludes:
In conclusion, the IdP should continue to function when its cookies are being defaulted to SameSite=Lax by browsers (currently tested on Chrome 78-81 and Firefox 72 with the same-site default flags set). Typically, we have only seen the IdP itself break when the JSESSIONID is set to SameSite 'Strict', which should not happen apart from when explicitly trying to set SameSite=None with older versions of Safari on MacOS <=10.14 and all WebKit browsers on iOS <=12 (https://bugs.webkit.org/show_bug.cgi?id=198181). However with regards to achieving single-sign-on you may see degraded operation, and the following possibilities occur:
The initial redirect requires using the cookie on the authorizing domain, whereas the domain requesting authorization isn't requesting a cookie - it's setting a cookie based on the POST
to it. So this should work with Lax
in theory since no cookies need to be available on the final POST
request - only the initial one. The final POST
redirect won't be able to have cookies sent on that request... but it doesn't need to - we're sending the token in the POST
request itself, and setting the cookie based on that. Genius!
Original SSO - requires SameSite=None
:
POST
s to auth providerNone
or undefined SameSite
) and creates temporary tokenRevised SSO - compatible with SameSite=Lax
:
GET
s to auth providerGET
now, not a POST
) and creates temporary tokenOne difference — that's it - GET
on the initial redirect, not POST
. This works because the initial redirect contains no sensitive information. This POST
could well have been a GET
. By making it one, we can bump up the security level for the entire session cookie, and any Remember Me cookies - not bad!
I've tested this in both Chromium 70 and Chrome 84 with the strict flags and third-party cookies blocked (so no "Lax + POST", it's just "Lax"). This does work. You can also set any Remember Me cookies to SameSite=Lax
as well - if the authenticator needs to use them to create a session spontaneously because no session was ongoing, the cookies to do so will be available as long as the redirect there was a GET
and not a POST
- so we're good!
SSO can work with Lax
. Obviously, XHR, dynamic CSS, websockets, etc. will not, but those could be trivially proxied behind the original domain. By utilizing GET
instead of POST
on the initial redirect, you can move to using cookies with SameSite=Lax
.
More complex SSO processes might be different - what I've given here is just a very simple SSO example. However, SSO and SameSite=Lax
are not mutually incompatible - you can make it work by slightly tweaking your SSO setup, and if you make other changes as needed, nothing will break.
Note that you can still do sessions with SameSite=Strict
- and if your entire site is on a single hostname and it's highly sensitive, I'd recommend that instead. But, if you need to do SSO, you can at least use SameSite=Lax
(but not Strict
, of course).