Search code examples
postcookiesgetsingle-sign-onsamesite

Feasibility of SSO with SameSite Lax cookies, only?


Background

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.

Why It Broke

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.

  1. 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?

  2. 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?

Mainstream Workaround

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?


Solution

  • 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: enter image description here

    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).

    1. 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.

    1. 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)

    1. User navigates to sub.example.org - not currently logged in because no cookie is set on this site
    2. 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.
    3. 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.
    4. 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.
    5. 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 POSTs 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 POSTing 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:

    1. Requester POSTs to auth provider
    2. Auth provider receives cookies (which requires None or undefined SameSite) and creates temporary token
    3. Auth provider redirects back to requester with token, which verifies it and creates session cookie

    Revised SSO - compatible with SameSite=Lax:

    1. Requester GETs to auth provider
    2. Auth provider receives cookies (because this is a GET now, not a POST) and creates temporary token
    3. 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).