The Same-site cookie atribute is a IETF draft written by Google Inc. 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:
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 , 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.
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.
SameSite Browser Support
The table below shows same-site cookie attribute compatibility amongst desktop browsers (see  for a complete list including mobile variants). It also includes an attempt to determine current/future browser support for IETF draft 'Incrementally Better Cookies '. 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.|
To test the affects of various SameSite settings, the following setup will be used:
- Chrome Canary 78.0.3885.0 (Official Build) canary (64-bit), with #same-site-by-default-cookies set to enabled. This is to mimic what will become the default behaviour in Chrome 80 onward.
- The IdP running in eclipse using the Java idp-testbed project.
- Using idp-jetty-base with Jetty v9.3.
- A new
SAML2Controller InitSSO POST method/endpoint. This overwrites the destination URL with
localhost, ignoring the baseURL of the servlet request.
- A modified hosts file, that points the hostname of shibtest.com to 127.0.0.1 (localhost)
SAML2Controller can then be accessed on a different top-level site than the IdP - to mimic a cross site request.
- The standard IdP Password authentication flow.
Using Client Side Session Storage, allowing HTML local storage.
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|
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_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|
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_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|
** 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.
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:
- All IdP session cookies which where set more than 2 minutes ago are treated as Lax. This then gives the same result as test 5 above, and the description in the section [SameSite Lax POST flow].
- Different combinations of JSESSIONID, shib_idp_session or shib_idp_session_SS are inside or outside of the 2 minute window. This leads to different types of behaviour; most commonly, as JSESSIONID is nearly (always) set first, the following would be true
- JSESSIONID=Lax+POST, shib_idp_session=Lax, shib_idp_session_ss=Lax. The JSESSIONID is sent cross-site with the post request but the shib_idp_session and shib_idp_session_ss are not. The storage services can be pulled out of the given session referenced by the JESSIONID, and hence
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.
SameSite Lax, POST flow, with client side session storage and HTML local storage option enabled
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.
- user-agent top-level site is
- (SO)(GET) user-agent navigates to
https://shibtest.com:8443/sp/SAML2/InitSSO-CS/POST. Which is a
same-origin request to initate a SAML 2 AuthN Request.
- BEGIN SAML AUTHENTICATION REQUEST ------
- (CS)(POST) The
sp/SAML2/InitSSO-CS/POST controller performs a
cross-site POST SAML authentication request to the IdP at
- SameSite is LAX, hence cookies for the localhost domain are NOT sent cross-site e.g. any existing
shib_idp_session or if present
shib_idp_session_ss cookies are not sent.
- The IdP processes the SAML request, is unable to load the client storage context as the JESSIONID was not sent, and the new one the container issues does not link to a session that already exists. Hence it goes in search of the session from HTML local storage (because it is enabled), and proceeds to the
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
- Note, the user-agent still has a top-level site of
- (CS)(GET) The user-agent responds to the redirect with a cross-site GET request to
https://localhost:8443/idp/profile/SAML2/POST/SSO?execution=e1s1. Which then displays the
- Webflow is using a POST-REDIRECT-GET strategy*. This cycle is happening cross-site, but the final GET request allows Lax SameSite cookies to be sent. Hence, the new
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.
- From here any subsequent requests occur same-origin so cookies are always sent.
- (SO)(POST) The user-agent then POSTS form data from the
client-storage-read view back to the IdP. This is a same-origin request, and contains all required cookies and session information.
- The flow continues and the
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
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]
Using Client Side Session Storage and Disabling HTML Local Storage.
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
shib_idp_session_ss cookie could be sent before the IdP tries to load the SSO session. Therefore, left without any SSO session information, the IdP renders the login page.
|Firefox 72.0.1 with sameSite default enabled||Yes||No||Yes, was expected a similar result to test 5. |
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|
Using Server Side Session Storage and Enabling/Disabling HTML Local Storage.
Four different POST requests where again tested, this time using server side session storage. The hypothesis being, under the following conditions, SSO will break:
- The IdP session cookies are treated as SameSite=Lax.
- The entire SAML2 SSO flow on the IdP is completed from the initial cross-site POST request from the SP - because the client-storage-read view is not required as client side storage is not used.
|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.
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:
- [client-side-storage] - If htmlLocalStorage is set to false, HTTP-POST SSO will not work (show login page again) with defaulted SameSite=Lax IdP cookies.
- [client-side-storage] - If htmlLocalStorage is set to true, and bean references in shibboleth.ClientStorageServices are left as they are, HTTP-POST SSO will work with defaulted SameSite=Lax
- [server-side-storage] - If htmlLocalStorage is set to true, and bean references in shibboleth.ClientStorageServices are left as they are, HTTP-POST SSO will work with defaulted SameSite=Lax.
- [server-side-storage] - If either htmlLocalStorage is set to false, or the bean references in shibboleth.ClientStorageServices are commented out, HTTP-POST SSO will not work (show login page again) with defaulted SameSite=Lax.
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.
SameSite and Single Logout
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 ):
- If “request”'s client is “null”, return “same-site”.
- Let “site” be “request”'s client’s “site for cookies” (as defined in the following sections).
- Let “target” be the registrable domain of “request”'s current url.
- If “site” is an exact match for “target”, return “same-site”.
- Return “cross-site”.
 Can I use… Support tables for HTML5, CSS3, etc