IBM Security Global Forum

 View Only

Cross-origin session detection

By Shane Weeden posted Thu March 05, 2020 12:00 AM

  

Consider a federated single sign-on environment where an Identity Provider (IDP) for applications may in turn be acting as a gateway – and be configured as a Service Provider (SP) to many different other IDPs. The role of this IDP is to provide a common federated SSO service to applications. It may also need to offer a combination of local authentication mechanisms and federated SSO authentication from other IDPs. We often see this type of scenario in companies that own several brands, or in mergers and acquisitions.

 

 

In the diagram above, idp.com is a federated SSO identity provider to all the applications. It also has a local directory of users, plus has the ability to accept federated users from idp1.comidp2.com, etc. You can imagine there could be many such additional IDPs.

When a browser user starting from an unauthenticated state (as far as idp.com is concerned) access one of the apps, they will be redirected to idp.com for authentication. At this point idp.com needs to decide what to do. The user is not currently authenticated to idp.com, so what happens next? The most common solution is to stop and ask them with a browser page – a process known as “Where are you from?“. The user needs to choose one of the remote identity providers (idp1.com, idp2.com, etc), or to login via the local registry. If there are a large number of partner IDPs, the user experience for the display of this dialog can get messy – this is called the “NASCAR problem” – in reference to the way so many sponsor stickers are stuck all over a racing car.

There are several approaches to try and limit the impact of the NASCAR problem.

One way is to first prompt for a username only – perhaps this is an email address and the domain of the email address is indicative of where the user’s home registry or IDP is located. The ibm.com public authentication site operates like this. IBM employees as well as some federated customers are redirected to their company-specific IDPs often located on an intranet that only the user’s browser can access anyway. Everyone else gets prompted for a local registry login via password and optional 2FA.

Another way to help with the NASCAR problem is to offer a “remember my decision” persistent cookie or local storage option so that a user on a personal device (which is most of us now – shared devices are far less common than they were 15 years ago) only has to go through the IDP selection process once. This type of approach is sometimes called an “ambient credential” – something of relatively low trust that at least identifies the user (or where they are from) to a point which allows a more streamlined authentication process on subsequent visits.

In this article I’m going to discuss another tool available to collaborating sites to help reduce the impact of the NASCAR problem. It’s not valuable in every situation, but it certainly is valuable in some, and not just when there are multiple IDPs to an identity gateway. The idea is to perform silent detection of any existing sessions at partner IDPs (idp1.com, idp2.com, etc) during the loading of the login page at idp.com and before any UI widgets are displayed. If an existing federated IDP session is detected it is possible to then automatically redirect to that IDP and leverage the federated relationship rather than prompting the user to stop and make a choice.

Of course there are situations where the user might not have an existing session at any of the partner IDPs, or might have sessions at more than one, or might not want to use the existing session but choose another. None of those situations are the point of this article – yes you need to cater for them however this can be done with user-experience design and subsequent workflows. I won’t be talking about them further in this article – instead this article is going to focus on a couple of different ways to technically perform cross-origin session detection. That is, from a page at idp.com, how can I tell if there is an existing session at idp1.com, without the browser’s top-level window URL leaving idp.com. This last requirement is important. If you are willing to allow the browser’s top-level URL to leave idp.com then you can use federated SSO flows such as OpenID Connect with “prompt=none” support to perform session detection, however many customers are unable or unwilling to do this particularly when there are multiple IDP partners or because their integration uses SAML which doesn’t have such a standards-based approach. Consequently this article will look at cross-origin approaches that don’t require the top-level browser page to leave idp.com to perform session detection at partner IDPs such as idp1.com, idp2.com, etc:

– Method 1 – Using cross-origin AJAX (XHR) or Fetch to access a protected resource at idp1.com
– Method 2 – Using an iframe and cross-origin window communication with postMessage

For the purposes of describing configuration requirements for this exercise, I will be using an ISAM system as idp1.com (the site from which detection of an existing session will be determined), and a hosted plain HTML page which contains embedded Javascript as simulating the login page from idp.com (the site which wants to detect the existing session at idp1.com).

Common configuration requirements

This section describes a couple of key configuration steps for idp1.com that need to be put in place regardless of which method of cross-origin session detection is being used.

Configuring 3rd-party session cookies

Regardless of which implementation method is used, if a cross-origin request is to successfully detect an existing session, the session cookie for the cross-origin site (idp1.com) must be included in either the AJAX call, or in requests to load an iframe source that originates from idp.com. That is, the session cookie from idp1.com needs to be used in a 3rd-party context. For our scenarios this means session cookies belonging to idp1.com must have the attributes ‘SameSite=None; Secure‘. This is part of a privacy-focused security initiative by the IETF and browser vendors to limit cross-site tracking by default unless a cookie is explicitly tagged to be permitted for cross-origin use (via SameSite=None; Secure) – more can be read about this here:

https://blog.chromium.org/2019/10/developers-get-ready-for-new.html

In the case of ISAM being the idp1.com implementation as in this article, this means explicitly tagging the ISAM session cookie (typically PD-S-SESSION-ID) with these attributes. ISAM has long had the ability to use the ‘Secure’ attribute (meaning the cookie may only be sent over https), however the ability to tag session cookies with an explicit SameSite directive and other custom cookie attributes has been added as a configuration option to ISAM in ISAM 9.0.7.1_IF3, and also in some earlier releases of ISAM as well via fixpack – please check with support for details on your version.

Bottom line – to implement a pattern like this, you need a version of ISAM supporting this stanza and set as follows:

[cookie-attributes]
PD-S-SESSION-ID = SameSite=None

Note that these attributes will be “added” to any existing attributes in Set-Cookie headers, so the cookie will still have “Secure” added by way of existing product behaviour.

Generally speaking I would not recommend setting SameSite=None without considering it’s implications, particularly for cross-site request forgery (CSRF). This article is not going to cover all the pros and cons of allowing cross-site cookies, however in controlled circumstances they are perfectly ok and in our scenario a necessity. What I’m saying here is do your research on cross-site cookies (also known as 3rd-party cookies), and consider what other URL-based resources your site exposes that might be exploitable in a 3rd party context before deciding to turn this feature on for ISAM session cookies.

URL-based ISAM AAC Authentication Policy Access

A common InfoMap authentication policy is used in ISAM in both methods described in this article. The examples shown in this article assumes that the following advanced configuration parameter is set:

sps.authService.policyKickoffMethod=path

This facilitates using a URL path rather than a query string parameter to decide which authentication policy to invoke. The reason URL path is preferred is that it makes it easier to attach URL-based ACL policies, HTTP transformation rules, etc in ISAM. You can make the techniques in this article work without path-based invocation however other configuration (such as how transformation rules are configured) will need to be altered. I am only going to show path-based configuration here.

The InfoMap authentication mechanism and policy may as well be configured now – like I said above both methods make use of it, and the configuration is identical. There is a lot of information already in the public domain about configuring InfoMap mechanisms and policies in ISAM – I am not going to cover that again here, but instead will just provide the resources necessary to configure the scenario. For background on using InfoMap see resource such as:

You will find all the resources associated with this article at: https://github.com/sbweeden/blog_assets/tree/master/cross_origin_sessions.

 

Configure an InfoMap authentication mechanism and policy (the policy should contain only the one InfoMap mechanism), with:

Mapping Rule: infomap_amiauthenticated.js
Page templates: amiauthenticated.html, amiauthenticated.json (load both the .html and .json files into the same template pages directory C/authsvc/authenticator/amiauthenticated/…)
Authentication policy name: AmIAuthenticated
Authentication policy URI: urn:ibm:security:authentication:asf:amiauthenticated

Notice that the infomap_amiauthenticated.js javascript code determines if the user is authenticated or not by looking up the username attribute from context. The page template in turn uses a boolean macro @AUTHENTICATED@ to indicate whether or not the user is authenticated. For the AJAX-based method, only the amiauthenticated.json page template is actually used. For the iframe method, only the amiauthenticated.html page template is used. More will be explained about this later.

Now let’s get into some of the specific configuration requirements and implementation details for the two methods covered in this article.

Method 1 – Using cross-origin AJAX with XHR or Fetch

In this section I’m going to only be demonstrating use of the fetch Javascript function for cross-origin https requests. This supersedes the use of XMLHTTPRequest (XHR), and we all need to move with the times!

Testing apiauthsvc access to the AmIAuthenticated InfoMap

Assuming you have the AmIAuthenticated InfoMap configured in ISAM (our site for idp1.com), test the policy using curl – first without a session cookie present, then again by “borrowing” a session cookie from an authenticated browser session (use the Network debugger in the browser to extract the PD-S-SESSION-ID cookie value). Notice the different HTTP response bodies in each case. Also notice that we are using the “apiauthsvc” instead of “authsvc“. The apiauthsvc is designed for programmatic clients (AJAX is programmatic), and will return a JSON response (hence the amiauthenticated.json page template). As previously mentioned, the ISAM system is also configured to use sps.authService.policyKickoffMethod=path so that authentication service policies are accessed via a URL path and not a query string with the PolicyId parameter.

Without an authenticated cookie:

curl -i https://idp1.com/mga/sps/apiauthsvc/policy/amiauthenticated

HTTP/1.1 200 OK
content-language: en-US
content-type: application/json
date: Wed, 04 Mar 2020 23:47:29 GMT
p3p: CP="NON CUR OTPi OUR NOR UNI"
server: IBM Security Access Manager
transfer-encoding: chunked
x-frame-options: SAMEORIGIN
cache-control: no-cache
expires: Thu, 01 Dec 1994 16:00:00 GMT
strict-transport-security: max-age=31536000; includeSubDomains
Set-Cookie: AMWEBJCT!%2Fmga!JSESSIONID=0000MtIqhHpGJ-1uqB81iztBa3m:af4de979-5d62-4bfd-a660-e11e05081a38; Path=/; Secure; HttpOnly
Set-Cookie: PD_STATEFUL_e07919a4-56c3-11ea-9809-000c29334fea=%2Fmga; Path=/
Set-Cookie: PD-S-SESSION-ID=1_2_0_R8cgZMn3GjevgxJWrViVJl5ca435ReNrVeD9lwGq5DJui2EX; Path=/; SameSite=None; Secure; HttpOnly

{"authenticated":false}

With an authenticated cookie (I got this PD-S-SESSION-ID cookie value from looking at Network browser trace on an authenticated browser session):

curl -i -H "Cookie: PD-S-SESSION-ID=1_2_1_K+urkzqVJjULoG84ONOUq87PMceJsbxdkI0E2ovW2g6MTIKy" https://idp1.com/mga/sps/apiauthsvc/policy/amiauthenticated

HTTP/1.1 200 OK
content-language: en-US
content-type: application/json
date: Wed, 04 Mar 2020 23:48:41 GMT
p3p: CP="NON CUR OTPi OUR NOR UNI"
server: IBM Security Access Manager
transfer-encoding: chunked
x-frame-options: SAMEORIGIN
cache-control: no-cache
expires: Thu, 01 Dec 1994 16:00:00 GMT
strict-transport-security: max-age=31536000; includeSubDomains
Set-Cookie: AMWEBJCT!%2Fmga!JSESSIONID=0000CBRsC2wPsnheeoE3pxwIDW_:af4de979-5d62-4bfd-a660-e11e05081a38; Path=/; Secure; HttpOnly
Set-Cookie: PD_STATEFUL_e07919a4-56c3-11ea-9809-000c29334fea=%2Fmga; Path=/

{"authenticated":true}

Enabling CORS Policy with HTTP Transformation

Even though these requests and responses work fine using curl, cross-origin AJAX requests from https://idp.com are not going to work … yet. For fetch to permit the cross-origin caller to access the HTTP status code and response body with Javascript using cross-origin requests, Cross-Origin Resource Sharing (CORS) policy must be configured at the cross origin site (idp1.com). That is necessary in our scenario, otherwise the client-side javascript loaded from https://idp.com will see only an opaque response and not be able to determine if the user is authenticated at idp1.com. For details on understanding this, I recommend reading:

https://developer.mozilla.org/en-US/docs/Web/API/Request/mode

To return CORS response headers from idp1.com on ISAM, we will use a HTTP transformation rule. This is provided in the github repo as the file: cors-response-headers.xsl

You will first need to update this file to set your own partner web origin (instead of https://idp.com) in the value for the Access-Control-Allow-Origin header. It should be attached as a response transformation rule to the URL for the InfoMap policy used above. There are comments at the start of the file showing example configuration for the ISAM reverse proxy configuration file.

Now if you perform a curl command you should see extra access-control-allow-origin and access-control-allow-credentials headers in the response, for example:

curl -i -H "Cookie: PD-S-SESSION-ID=1_2_1_K+urkzqVJjULoG84ONOUq87PMceJsbxdkI0E2ovW2g6MTIKy" https://idp1.com/mga/sps/apiauthsvc/policy/amiauthenticated

HTTP/1.1 200 OK
content-language: en-US
content-type: application/json
date: Wed, 04 Mar 2020 23:48:41 GMT
p3p: CP="NON CUR OTPi OUR NOR UNI"
server: IBM Security Access Manager
transfer-encoding: chunked
x-frame-options: SAMEORIGIN
cache-control: no-cache
expires: Thu, 01 Dec 1994 16:00:00 GMT
access-control-allow-origin: https://idp.com
access-control-allow-credentials: true
strict-transport-security: max-age=31536000; includeSubDomains
Set-Cookie: AMWEBJCT!%2Fmga!JSESSIONID=0000CBRsC2wPsnheeoE3pxwIDW_:af4de979-5d62-4bfd-a660-e11e05081a38; Path=/; Secure; HttpOnly
Set-Cookie: PD_STATEFUL_e07919a4-56c3-11ea-9809-000c29334fea=%2Fmga; Path=/

{"authenticated":true}

If you don’t see those access-control-… headers, stop and fix it. There’s no point in continuing as cross-origin fetch will not work without them.

Now let’s take a look at the fetch implementation code in the idp.com site. The example is reproduced here, but also available in the github repository as the file idp_login_fetch.html:

<html>
<head>
<script type="text/javascript">

	var isamSite = "https://idp1.com";
	var amiauthenticated = isamSite + "/mga/sps/apiauthsvc/policy/amiauthenticated";

	function displayMessage(msg) {
		document.getElementById("msgdiv").textContent = msg;
	}

	function detectLogin() {
		fetch( amiauthenticated, 
		{
			method: 'GET',
			mode: 'cors',
			credentials: 'include',
			headers: {
				'Accept': 'application/json'
			}
		}).then((response) => {
			console.log("Received response status: " + response.status);
			return response.json();
		}).then((data) => {
			displayMessage("Authenticated: " + data.authenticated);
		}).catch((error) => {
			console.log("Received error: " + error);
		});
	}

</script>
</head>
<body onload="detectLogin()">
<div id="msgdiv">
	One moment...
</div>
</body>
</html>

Note that when the browser page loads, the cross-origin fetch is initiated, and then a simple display message informs you whether or not an authenticated session was detected at the cross-origin site (https://idp1.com). In a real federated SSO scenario instead of displaying a message you may well choose to just redirect the browser:

window.location = <url_to_kickoff_federated_sso>;

That’s all you need to know about using cross-origin AJAX with fetch to perform session detection. Let’s now look at an alternative method that uses iframes and cross-window postMessage to achieve the same scenario.

Method 2 – Using an iframe and cross-origin window communication with postMessage

This technique is subtly different from the previous AJAX-based technique in the following ways:

  • A different HTTP transformation rule is used. No CORS headers are required as no cross-origin fetch is being performed. Instead header control is used to permit iframe’ing the idp1.com content in a page loaded from idp.com.
  • The iframe solution makes use of cross window communication with postMessage.

Testing authsvc access to the AmIAuthenticated InfoMap

First make sure you have the AmIAuthenticated InfoMap policy configured as per the common configuration section. This method with the iframe is going to make use of the amiauthenticated.html page template – notice the use of /authsvc/ in the url instead of /apiauthsvc/ as in the previous method. Use curl to ensure you can access:

curl -i https://idp1.com/mga/sps/authsvc/policy/amiauthenticated

Enabling Content Security Policy with HTTP Transformation

Next, configure the HTTP transformation rule iframe-headers.xsl. This is needed to remove the default x-frame-options: SAMEORIGIN header from responses when the policy is accessed (ISAM AAC inserts this by default), and instead insert a Content-Security-Policy header to instruct the browser that it is ok for idp.com to frame this page from idp1.com. You will have to first edit the http transformation rule to change https://idp.com to your own site URL.

Note also this can be put in place alongside the previous example because the URL path is different. The iframe approach uses /authsvc/ rather than /apiauthsvc/. Both these methods can be configured at the same time with no issues.

After the HTTP transformation rule is in place, try curl to check that the correct headers and page template is being returned. In particular look for the absence of x-frame-options, the presence of content-security-policy, and that the HTML page template is being returned.

 

curl -i https://idp1.com/mga/sps/authsvc/policy/amiauthenticated

HTTP/1.1 200 OK
content-language: en-US
content-type: text/html;charset=UTF-8
date: Thu, 05 Mar 2020 02:05:55 GMT
p3p: CP="NON CUR OTPi OUR NOR UNI"
server: IBM Security Access Manager
transfer-encoding: chunked
cache-control: no-cache
expires: Thu, 01 Dec 1994 16:00:00 GMT
content-security-policy: frame-ancestors https://idp.com;
strict-transport-security: max-age=31536000; includeSubDomains
Set-Cookie: AMWEBJCT!%2Fmga!JSESSIONID=0000ZwA2YvirfFKxTO8oemWMHoM:af4de979-5d62-4bfd-a660-e11e05081a38; Path=/; Secure; HttpOnly
Set-Cookie: PD_STATEFUL_e07919a4-56c3-11ea-9809-000c29334fea=%2Fmga; Path=/
Set-Cookie: PD-S-SESSION-ID=1_2_0_MV1oN-x69mz4Z3KqRfksPauGcfjhlm3qRG7OkNuyV+7+Jtjt; Path=/; SameSite=None; Secure; HttpOnly

<html>
<body>
Authenticated: false
<script type="text/javascript">
	// This only used for cross-origin iframe scenario
	var msg = { "authenticated": false };
	window.parent.postMessage(msg, "*");
</script>
</body>
</html>
<SCRIPT type="text/javascript">
/*<![CDATA[*/ 
document.cookie = "IV_JCT=%2Fmga; path=/; secure";
/*]]>*/ 
</SCRIPT>

 

Now let’s take a look at the HTML content that is being hosted on https://idp.com as part of the login form. I have included this example page in github as idp_login_iframe.html:

 

	
<html>
<head>
<script type="text/javascript">

var isamSite = "https://idp1.com";
var amiauthenticated = isamSite + "/mga/sps/authsvc/policy/amiauthenticated";

function displayMessage(msg) {
	document.getElementById("msgdiv").textContent = msg;
}

function receiveMessage(event) {
	if (event.origin == isamSite) {
		var authenticated = event.data.authenticated;
		displayMessage("Authenticated: " + authenticated);
	}
}

function detectLogin() {
	window.addEventListener("message", receiveMessage, false);
	var myframe = document.createElement("iframe");
	myframe.setAttribute("src", amiauthenticated);
	document.getElementById("detectLoginDiv").innerHTML = "";
	document.getElementById("detectLoginDiv").appendChild(myframe);
}

</script>
</head>
<body onload="detectLogin()">
<div id="msgdiv">
	One moment...
</div>
<div id="detectLoginDiv" style="display:none">
</div>
</body>
</html>

 

On load, detectLogin() is called. This sets up an event listener (which is invoked on postMessage from the iframe), then adds the iframe to the hidden detectLoginDiv.

At runtime, the iframe will load and because of the content-security-policy and the 3rd-party cookie enablement of the PD-S-SESSION-ID cookie from idp1.com, the iframe will include detection of whether or not an authenticated session exists at the site. Finally it will communicate this to the parent window via postMessage javascript that you can see in the amiauthenticated.html page template. The receiveMessage() event handler will fire, and similar to the previous method will simply display what it learned. If the session was authenticated, then of course the browser could be redirected for federated SSO in the same way as described in the AJAX method above.

The Wrap

In this article I’ve described a couple of different ways that cross-origin session detection can be determined without having to do up-front full-page redirects to another site via an SSO protocol. Each has different security-based configuration requirements, and both require the ISAM session cookie to be enabled for 3rd-party use. Provided adequate CSRF protection exists for all resources accessible from the ISAM site, 3rd-party cookie enablement is ok. In these scenarios it is necessary.

I don’t particularly recommend one over the other, although an argument can be made that the AJAX method may be simpler since in theory any existing protected resource (rather than a dedicated InfoMap) could be used to perform session detection. It would still however require CORS configuration. The iframe method requires the content hosted on idp1.com to include the Javascript for the postMessage call and is therefore slightly more of a dedicated configuration.

I’d like to hear your feedback on these scenarios – and of course am happy to help with any questions you might have about IBM’s web access management technologies.


#SecurityExpert

0 comments
18 views

Permalink