The testing methodology has been updated. All previous tests were run with the IdP configured to allow the storage of sessions in client side HTML local storage (idp.storage.htmlLocalStorage=true). This has the effect of enabling SSO when using the HTTP-POST binding even when cookies are defaulted to SameSite=Lax, or even when using server side session storage. Without it, SSO will fail when initiated using the HTTP-POST binding from a cross-site SP, and the user will be always presented with the login page. |
This work has been triggered by the Google IETF draft 'Incrementally Better Cookies' [6], which aims to default all cookies without a same-site flag to SameSite=Lax, and requiring SameSite=None cookies to be sent over secure transport (HTTPS). This will become the default in Chrome 80, which will be released approximately February 2020. |
The Same-site cookie atribute is a IETF draft written by Google Inc. [1][2]which, at its most strict, instructs the user-agent not to send (attach) a SameSite cookie during a cross site HTTP request. The principal aim of which is to help prevent forms of cross site request forgery that ‘ride’ ambient authority session cookies.
A cross site request is, abstractly (see appendix A for the draft algorithmic definition), one where the top level site (typically that shown in an address bar) changes during navigation.
There are three values for the same-site cookie attribute:
None - send cookies for all ‘same-site’ and ‘cross-site’ requests.
Note, there is an updated draft [3] which defines two new SameSite values ‘FirstPartyLax’ and ‘FirstPartyStrict’, which are used in relation to ‘First-Party sets’ (groupings of registered domains to a single controlled entity). These values are not used here, and will be discussed in follow up implementation work - although at first glance, this seems unlikely to be used in a dynamic SSO federation. |
Chrome 76 and onwards contain a flag to enable the treatment of cookies without a SameSite attribute to be SameSite=Lax. This will become the default in Chrome 80 [4], which has an approximate release date of February 2020 - see the section below for an overview of browser support. The aim of this document is to test the affects of such change on the IdP. Our immediate concern is, if the session cookies used by the IdP (depending on configuration) e.g. JSESSIONID
, or shib_idp_session
, do not specify the SameSite attribute - and are therefore defaulted to SameSite=Lax - and are being used in a cross-site POST request from an SP to the IdP, will it break either the IdP and or single-sign-on as potentially the session cookies will not be sent.
Further work will investigate a Proof of Concept servlet filter for munging appropriate IdP session cookies in order to set their SameSite attribute to None - thus maintaining their current behaviour. This is only necessary because the Java Servlet Specification v3.0 or v4.0 does not cater for the SameSite attribute, and it can not be set through the Java Cookie API.
The table below shows same-site cookie attribute compatibility amongst desktop browsers (see [5] for a complete list including mobile variants). It also includes an attempt to determine current/future browser support for IETF draft 'Incrementally Better Cookies '[6]. That is, defaulting same-site unset cookies to SameSite=Lax, and requiring SameSite=None cookies to be sent over secure transport (HTTPS).
The table will be updated as and when information becomes available.
Browser | SameSite Support | Can enable SameSite Lax By Default | Can enable SameSite None requires Secure by default | SameSite Lax enabled in browser by default | SameSite None requires secure enabled in browser by default |
---|---|---|---|---|---|
Firefox desktop | v60 onwards | in advanced setting/preferences (about:config) from v69 | in advanced setting/preferences (about:config) from v69 | unknown but likely to happen | unknown but likely to happe |
Chrome desktop | v51 onward | in experimental features (chrome://flags) from v76 | in experimental features (chrome://flags) from v76 | Stable Chrome 80 (aprox. Feb 4th 2020,https://chromiumdash.appspot.com/schedule). Although likely to be the default in Chrome Beta 79. | Stable Chrome 80 (aprox. Feb 4th 2020,https://chromiumdash.appspot.com/schedule).Although likely to be the default in Chrome Beta 79. |
Safari desktop | v12 onward** | unknown | unknown | unknown | unknown |
Opera desktop | v39 onward | in experiments (opera://flags/) from 63 onward | in experiments (opera://flags/) from 63 onward | likely to follow chrome as chromium/blink based. | likely to follow chrome as chromium/blink based. |
Edge | v16 onward | possible in Edge builds based on chromium 76 onward (2019). Not sure about older EdgeHTML engines. In experimental features (edge://flags) | possible in Edge builds based on chromium 76 onward (2019). Not sure about older EdgeHTML engines - looks like that will NOT happen in IE or Edge <v18. In experimental features (edge://flags) | likely to follow chrome as in 2019 Edge became chromium/blink based. | likely to follow chrome as in 2019 Edge became chromium/blink based. |
Webkit based browsers on Mac (safari) and iOS (safair, chome, firefox etc) are currently affected by a bug that treats SameSite=None or SameSite=nonesense cookies as SameSite=Strict (https://bugs.webkit.org/show_bug.cgi?id=198181). We believe the fix for this will only take effect from MacOS 10.15 and iOS 13. Consequently, any attempt to maintain the current functional behaviour of cookies by setting SameSite=None on unpatched versions of Webkit will break SSO. This bug also affects older Chrome and Android browsers. |
To compound matters, Firefox has a bug filed (https://bugzilla.mozilla.org/show_bug.cgi?id=1465402) that suggests they may consider the ability to set even Lax cookies in the middle of SP→ IdP → IdP redirect chains to be a bug. If they decide to "fix" this non-bug, it will cause the current default behavior to stop functioning correctly at all with Firefox because the JSESSIONID cookie set upon initial contact to the IdP will be ignored, and the follow-on request will trigger a flow exception error. This would make "doing nothing" a non-option and create a hopeless situation without ugly workarounds to accomodate three different non-overlapping modes of behavior. |
To test the affects of various SameSite settings, the following setup will be used:
SAML2Controller
InitSSO POST method/endpoint. This overwrites the destination URL with localhost
, ignoring the baseURL of the servlet request.SAML2Controller
can then be accessed on a different top-level site than the IdP - to mimic a cross site request.The IdP is configured here to use client side session storage with, importantly, idp.storage.htmlLocalStorage set too true. The table below shows the results of various tests using different SAML2.0 bindings or initiators. The shib_idp_session_ss is less relevant, and could use local storage if supported by the browser with similar same effect.
Of note, this does not test the affect of any SP cookies sent cross-site from the IdP.
No | Test | SameSite Value | Results | Browser | IdP Working | SSO Working | Expected Result |
---|---|---|---|---|---|---|---|
1 | GET - Unsolicited SSO Cross Site | JSESSIONID=Strict, shib_idp_session=Lax, shib_idp_session_ss=Lax | JSESSIONID Cookie is not sent cross site. The flow can not be resumed during the GET-REDIRECT-GET for the client-storage-read view-state. That is, the JSESSIONID is SET on response from the inital GET request, but is not sent cross-site on the REDIRECT GET request, hence webflow can not resume the conversation | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | No | No | Yes |
2 | GET - Unsolicited SSO Cross Site | JSESSIONID=Lax, shib_idp_session=Lax, shib_idp_session_ss=Lax | All cookies are sent cross site. | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | Yes |
3 | GET - Unsolicited SSO Cross Site | Not Set (defaults to Lax) | All cookies are sent cross site. Chrome is treating all cookies not set with SameSite as Lax, as enabled by the #same-site-by-default-cookies chrome flag | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | Yes |
4 | POST - SAML 2.0 Post Binding | JSESSIONID=Strict, shib_idp_session=Lax,shib_idp_session_ss=Lax | The IdP goes in search of the session from HTML local storage (because it is enabled), hence webflow POST-REDIRECT-GET cycle on the initial client-storage-read view, JSESSIONID is SET on REDIRECT, but not sent on GET, hence the flow can not be resumed from the client-storage-read view | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | No | No | Yes |
5 | POST - SAML 2.0 Post Binding | JSESSIONID=Lax, shib_idp_session=Lax, shib_idp_session_ss=Lax | The IdP goes in search of the session from HTML local storage (because it is enabled), hence webflow performs a POST-REDIRECT-GET cycle on the initial client-storage-read view, JSESSIONID is SET on REDIRECT, and is sent along with the shib_idp_session and shib_idp_session_ss on GET, hence the flow can be resumed, the shib_idp_session_ss is reloaded, matched to the shib_idp_session and SSO performed | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | No, expecting to fail because of the initial POST request. See the detailed description in section [SameSite Lax POST flow] below |
6 | POST - SAML 2.0 Post Binding | Not Set (defaults to Lax) | Chrome is treating all cookies not set with SameSite as Lax, as enabled by the #same-site-by-default-cookies chrome flag. The IdP goes in search of the session from HTML local storage (because it is enabled), hence webflow POST-REDIRECT-GET cycle on the initial client-storage-read view, JSESSIONID is set on REDIRECT, and is sent along with the shib_idp_session and shib_idp_session_ss on GET, hence the flow can be resumed, the shib_idp_session_ss is reloaded, matched to the shib_idp_session and SSO performed. Although this is not consistent. | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes/No | No, expecting to fail because of the initial POST request. See the note below, behaviour is not consistent |
7 | POST - SAML 2.0 Post Binding | JSESSIONID=Lax, shib_idp_session=None, shib_idp_session_ss=Lax | A new JSESSIONID is set during the initial Webflow POST-REDIRECT-GET cycle on the initial client-storage-read view, the shib_idp_session is sent with every request, the shib_idp_session_ss is sent on the final GET request. SSO is performed | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | Yes, all requests carry the shib_idp_session required for SSO, the initial POST-REDIRECT-GET cycle creates and then sends the JSESSIONID to progress the webflow conversation |
8 | GET - Redirect | JSESSIONID=Lax, shib_idp_session=Lax,shib_idp_session_ss=Lax | All cookies are sent cross-site | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | Yes |
9 | GET - Redirect | Not Set (defaults to Lax) | All cookies are sent cross-site | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | Yes |
** This is only the case if the Client Session Storage Service beans are removed from the shibboleth.ClientStorageServices list. Otherwise, the PopulateClientStorageLoadContext will load session information from the client using the client-storage-read view-state, which will then send the shib_idp_session cookie same-origin after that view-state has posted back to the IdP.
IMPORTANT:
It appears that current versions of Chrome are treating defaulted (SameSite not set) cookies as SameSite Lax + POST for a period of 2 minutes from when they are set, then SameSite Lax thereafter Issue 990439 - chromium - An open-source project to help move the web forward. - Monorail. Within this window, the the IdP will operate normally, as if there is no SameSite restriction. Otherwise, there are two general possibities:
client-storage-read
is not required to reload it. The flow continues without the POST-REDIRECT-GET cycle of a view-state, hence the shib_idp_session cookie is not sent and the previous authentication result can not be retrieved from the IdPSession. As a result, the username and password login view is presented - thus breaking SSO.The following outlines the network interactions with the IdP for a POST SAML SSO request. The user has previously authenticated using the standard Password flow, and in so doing obtained shib_idp_session
and JSESSIONID cookies. The IdP is using client side session storage, either as a cookie or in HTML local storage.
SO = Same-origin request.
CS = Cross-site request.sp/SAML2/InitSSO-CS/POST
= is a new controller endpoint that overrides destination address with ‘localhost’ to mimic a cross-site request.
https://shibtest.com:8443
.https://shibtest.com:8443/sp/SAML2/InitSSO-CS/POST
. Which is a same-origin
request to initate a SAML 2 AuthN Request.shibtest.com
sp/SAML2/InitSSO-CS/POST
controller performs a cross-site
POST SAML authentication request to the IdP at https://localhost:8443/idp/profile/SAML2/POST/SSO
.JSESSIONID
, shib_idp_session
or if present shib_idp_session_ss
cookies are not sent.LocalStorageRead
view-state, commits a redirect response (https://localhost:8443/idp/profile/SAML2/POST/SSO?execution=e1s1
) to the user-agent, and issues a new JSESSIONID
.https://shibtest.com:8443
.https://localhost:8443/idp/profile/SAML2/POST/SSO?execution=e1s1
. Which then displays the client-storage-read.vm
page.JSESSIONID
obtained in the inital POST-REDIRECT response is sent with the final GET request and the flow can resume. Note, the shib_idp_session cookie is sent with the final GET cross-site request but that is not strictly that important at this stage.client-storage-read
view back to the IdP. This is a same-origin request, and contains all required cookies and session information.shib_idp_sesison
cookie is used to find a previous IdPSession
from the loaded (from cookie or local storage) shib_idp_session_ss
storage record during the PopulateSessionContext
action using the StorageBackedSessionManager
.ExtractActiveAuthenticationResults
action then extracts the AuthenticationResult
from the IdpSession
and, providing certain criteria are satisfied, enables SSO.[*][By default when a flow enters a view state, it executes a client-side redirect before rendering the view]
Here we test again, this time disabling the IdPs ability to use HTML local storage to store session information, instead only using cookies e.g. setting idp.storage.htmlLocalStorage to false.
No | Test | SameSite Value | Results | Browser | IdP working | SSO Working | Expected Result |
---|---|---|---|---|---|---|---|
10 | POST - SAML 2.0 Post Binding | JSESSIONID=Strict, shib_idp_session=Lax,shib_idp_session_ss=Lax | The IdP does not receive the JSESSIONID cross-site, it can not resume an IdPSession, hence it tries to load the DisplayUsernamePasswordPage (login) page. Webflow performs a POST-REDIRECT-GET cycle to render the login page, but because it can not resume the flow on the final GET request (because the JSESSIONID is still not sent cross-site), Webflow is unable to resume the flow and throws an error. | Firefox 72.0.1 with sameSite default enabled | No | No | Yes |
11 | POST - SAML 2.0 Post Binding | JSESSIONID=Lax, shib_idp_session=Lax, shib_idp_session_ss=Lax | No cookies are sent cross-site, however the IdP no longer goes in search of the session from HTML local storage on the browser, and hence does not render the client-storage-read view. As a result, there are no same-site requests to the IdP where the the | Firefox 72.0.1 with sameSite default enabled | Yes | No | Yes, was expected a similar result to test 5. |
12 | POST - SAML 2.0 Post Binding | Not Set (defaults to Lax). | Same results as 11. | Firefox 72.0.1 with sameSite default enabled | Yes | No | No, expecting to fail because of the initial POST request. See the note below, behaviour is not consistent |
Four different POST requests where again tested, this time using server side session storage. The hypothesis being, under the following conditions, SSO will break:
No | Test | SameSite Value | Results | Browser | IdP working | SSO Working | Expected Result |
---|---|---|---|---|---|---|---|
13 | POST - SAML 2.0 Post Binding (Server Side Session Storage). Client storage services list not empty. HTML local storage set too true. | JSESSIONID=Lax, shib_idp_session=Lax | No cookies sent cross-site, but the client-storage-read view is rendered because the idp.storage.htmlLocalStorage option is set to true, and the shib_idp_session cookie is sent on subsequent same-origin requests from that view. Hence, the shib_idp_session can be used to retrieve a server side IdPSession and SSO occurs. | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | No |
14 | POST - SAML 2.0 Post Binding (Server Side Session Storage). ClientStorageServices list empty. HTML local storage set too true. | JSESSIONID=Lax, shib_idp_session=Lax | No cookies sent cross-site, the IdP does not attempt to load session from client HTML storage because the bean references in shibboleth.ClientStorageServices are commented out (see below), hence the client-storage-read view-state is not required, therefore the shib_idp_session cookie is not available when the session is loaded by the sessionResolver during the PopulateSessionContext action. This breaks SSO, instead showing the user the login page. | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | No | Yes |
15 | POST - SAML 2.0 Post Binding (Server Side Session Storage). ClientStorageServices list empty. HTML local storage set too true. | JSESSIONID=Lax, shib_idp_session=None | The entire flow is executed from the initial SP post request up until the SAML Request is posted back to the SP (via a view-state). A new JSESSIONID is created, the shib_idp_session is sent cross-site allowing the IdPSession to be retrieved, and SSO occurs. | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | Yes | Yes |
16 | POST - SAML 2.0 Post Binding (Server Side Session Storage). .ClientStorageServices list not empty. HTML local storage set too false. | JSESSIONID=Lax, shib_idp_session=Lax | No cookies sent cross-site, the IdP does not attempt to load session from client HTML storage because idp.storage.htmlLocalStorage is set to false. Hence the client-storage-read view-state is not required, therefore the shib_idp_session cookie is not available when the session is loaded by the sessionResolver during the PopulateSessionContext action. This breaks SSO, instead showing the user the login page. | Chrome 78.0.3888.0 (Official Build) canary (64-bit) | Yes | No | Yes |
Contradictory to the hypothesis, test 13 shows SSO working even though the initial cross-site POST request does not send any of the IdP session cookies. This occurs because of the configuration of the IdP, that is; even if server side session storage is configured in idp.properties (idp.session.StorageService), the client-storage-read view is still rendered provided the idp.storage.htmlLocalStorage property is set to true, and the list of shibboleth.ClientStorageServices still contains reference to the various client storage services e.g.
idp.properties: idp.storage.htmlLocalStorage = true session-manager.xml: <util:list id="shibboleth.ClientStorageServices"> <ref bean="shibboleth.ClientSessionStorageService" /> <ref bean="shibboleth.ClientPersistentStorageService" /> </util:list> |
Of note, the IdP is still storing session information on the server even if those properties/beans are enabled.
As per XML documentation surrounding this list, If server side storage is used these bean references can be safely removed. However, If they are removed, or the idp.storage.htmlLocalStorage is set to false, the results in test 14 are observed and SSO breaks. That is to say, if server side session storage is used AND IdP session cookies are treated as SameSite=Lax (defaulting in chrome 80), and EITHER those bean references are removed OR idp.storage.htmlLocalStorage is disabled, SSO will break.
Test 15 shows what happens if the shib_idp_session cookie is set to SameSite=None. This enables SSO, even if the JSESSIONID is set to SameSite=Lax and is not sent in the initial cross-site request.
In conclusion, IdP deployers using server side session storage should not experience issues with SSO for defaulted SameSite=Lax IdP session cookies using the HTTP-POST SAML2 binding, providing they have not removed those bean references and the idp.storage.htmlLocalStorage is enabled.
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:
Therefore to try and guarantee SSO on existing installations of the IdP v3.X you could enabled the HTML Local Storage plugin whether you use client-side storage or server-side storage by setting the idp.storage.htmlLocalStorage property to true in idp.properties, see StorageConfiguration for more information and any implications.
SP Front-channel SLO propagation from the IdP is facilitated using iframes, javascript, and in some cases HTML5 storage [LogoutConfiguration]. iframes that send SLO requests to SPs do not trigger top level navigation changes to the main browser document/window. As a result, same-site cookies will not be sent cross-site within the iframe - even when using 'safe' HTTP GET requests - unless the cookie is SameSite=None. Consequently, in cases where a principal's session state exists solely in a user agent in the form of a cookie, the SP must set the same-site attribute on the cookie to None - this is the responsibility of the SP.
For an SLO request to a Shibboleth SP the SP session cookie is optional, requiring only the NameID in the request, as a result SLO is unaffected for either HTTP-Redirect or HTTP-POST bindings.
The SOAP SLO binding uses backchannel communication and hence cookies are not relevant in this context.
the following algorithm returns “same-site” or “cross-site” (see [1]):
(1) https://tools.ietf.org/html/draft-west-first-party-cookies
(2) https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#page-42
(3) https://tools.ietf.org/html/draft-west-cookie-samesite-firstparty-00
(4) https://www.chromestatus.com/feature/5088147346030592
[5] Can I use… Support tables for HTML5, CSS3, etc
(6) https://datatracker.ietf.org/doc/draft-west-cookie-incrementalism/