TL;DR - Yes, you can use SameSite=Lax
(but not SameSite=Strict
) and not break SSO!
There are two big things to note about SameSite
cookies:
- Lax prohibits cross-site requests using
POST
- Strict also prohibits cross-site requests using
GET
A helpful summary:

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 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?
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
and Strict
?
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.
Original SSO process (requires SameSite=None
)
- User navigates to
sub.example.org
- not currently logged in because no cookie is set on this site
- Page detects not signed in and automatically redirects to a page for SSO on
example.com
- if the user is not authenticated there, it redirects back and gives a username/password prompt. If the user is authenticated, there, continue.
- On
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.
- Back on
sub.example.org
, read the token that was POSTED and query the database for that token, and from that retrieve the user ID.
- Set the user ID in a local session on
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.
Workaround
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!
Revised SSO Process
Original SSO - requires SameSite=None
:
- Requester
POST
s to auth provider
- Auth provider receives cookies (which requires
None
or undefined SameSite
) and creates temporary token
- Auth provider redirects back to requester with token, which verifies it and creates session cookie
Revised SSO - compatible with SameSite=Lax
:
- Requester
GET
s to auth provider
- Auth provider receives cookies (because this is a
GET
now, not a POST
) and creates temporary token
- Auth provider redirects back to requester with token, which verifies it and creates session cookie
One 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!
Conclusion
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).