Single Sign-On – the basic concepts

What is Single Sign-On?

A good buzzword at least, but on top of that it’s a solution which lets users authenticate at one place and then use that same user session at many completely different applications without reauthenticating over and over again.
To implement SSO, OpenAM (as most other SSO solutions) uses HTTP cookies (RFC 6265 [1]) to track the user’s session. Before we go into any further details on the how, let’s step back first and get a good understanding first on cookies. Please bear with me here, believe me when I say that being familiar with cookies will pay out at the end.

Some important things to know about cookies

By using cookies an application can store information at the user-agent (browser) across multiple different HTTP requests, where the data is being stored in a name=value basic format. From the Cookie RFC we can see that there are many different extra fields in a cookie, but out of those you will mostly only run into these:

  • Domain – the domain of the cookie where this cookie can be sent to. In case the domain is not present, it will be handled as a host based cookie, and browsers will only send it to that _exact_ domain (so no subdomains! Also beware that IE may behave differently…[2]).
  • Max-Age – the amount of time the cookie should be valid. Of course IE doesn’t support this field, so everyone uses “Expires” instead with a GMT timestamp.
  • Path – a given URL path where the cookie applies to (for example /openam)
  • Secure – when used, the cookie can be only transferred through HTTPS, regular HTTP requests won’t include this cookie.
  • HttpOnly – when used, the cookie won’t be accessible through JavaScript, giving you some protection against XSS attacks

Let’s go into a bit more detail:

  • If you create a cookie with Domainexample.com“, then that Cookie will be available for an application sitting at foo.example.com as well, and basically for all other subdomains, BUT that very same cookie won’t be available at badexample.com nor at foo.badexample.com, because badexample.com does not match the example.com cookie domain. Browsers will only send cookies with requests made to the corresponding domains. Moreover browser only will set cookies for domains where the response did actually come from (i.e. at badexample.com the browser will discard Set-Cookie headers with example.com Domain).
  • Browsers will discard cookies created by applications on TLDs (Top Level Domain, like .co.uk or .com), also the same happens with cookies created for IP addresses, or for non valid domains (like “localhost”, or “myserver”), the Domain has to be a valid FQDN (Fully Qualified Domain Name) if present.
  • If you do not specify Max-Age/Expires, the cookie will be valid until the browser is closed.
  • In order to clear out/remove a cookie you need to create a cookie with the same name (value can be anything), and set the Expires property to a date in the past.
  • In case you request a page, but a Set-Cookie is coming out of a subsequent request (i.e. resource on the page – frame/iframe/etc), then that cookie is considered as a third party cookie. Browsers may ignore third party cookies based on their security settings, so watch out.
  • Containers tend to handle the cookie spec a bit differently when it comes to special characters in the cookie value. By default when you create a cookie value with an “=” sign in it for example, then the cookie value should be enclosed with quotes (“). This should be done by the container when it generates the Set-Cookie header in the response, and in the same way when there is an incoming request with a quoted value, the container should remove the quotes, and only provide the unquoted value through the Java EE API. This is not always working as expected (you may get back only a portion of the actual value, due to stripping out the illegal characters), hence you should stick with allowed characters in cookie values.

Funky IE problems

  • IE does not like cookies created on domain names that do not follow the URI RFC [3]
  • IE9 may not save cookies if that setting happened on a redirect. [4]

How is all of this related to SSO?

Well, first of all as mentioned previously OpenAM uses cookie to track the OpenAM session, so when you do regular SSO, then the sequence flow would be something like this:

SSO Authentication Flow

SSO Authentication Flow


And here is a quick outline for the above diagram:

  • User visits application A at foo.example.com, where the agent or other component realizes that there is no active session yet.
  • User gets redirected to OpenAM at sso.example.com, where quite cleverly the cookie domain was set to example.com (i.e. the created cookie will be visible at the application domain as well)
  • User logs in, as configured OpenAM will create a cookie for the example.com domain.
  • OpenAM redirects back to the application at foo.example.com, where the app/policy agent will see the session cookie created, and it will then validate the session, upon success, it will show the protected resource, since there is no need to log in any more.

Oh no, a redirect loop!

In my previous example there are some bits that could go wrong and essentually result in a redirect loop. A redirect loop 90+% of the time happens because of cookies and domains – i.e. misconfiguration. So what happens is that OpenAM creates the cookie for its domain, but when the user is redirected back to the application, the app/agent won’t be able to find the cookie in the incoming request, but authentication is still required, so it redirects back to OpenAM again. But wait, AM already has a valid session cookie on its domain, no need for authentication, let’s redirect back to the app, and this goes on and on and on. The most common reasons for redirect loops:

  • The cookie domain for OpenAM does not match at all with the protected application.
  • The cookie domain is set to sso.example.com, instead of example.com, and hence the cookie is not available at foo.example.com .
  • The cookie is using Secure flag on the AM side, but the protected application is listening on HTTP.
  • Due to some customization possibly, the AM cookie has a path “/openam” instead of the default “/”, so even if the cookie domain is matching the path won’t match at the application.
  • You run into one of the previously listed IE quirks. :)
  • Your cookie domain for OpenAM isn’t actually an FQDN.

So how does OpenAM deal with applications running in different domains? The answer is CDSSO (Cross Domain Single Sign-On). But let’s discuss that one in the next blog post instead. Hopefully this post will give people a good understanding of the very basic SSO concepts, and in future posts we can dive into the more complicated use-cases.

References

Implementing remember me functionality – part 2

In my last post we were trying to use the built-in persistent cookie mechanisms to implement remember me functionality. This post tries to go beyond that, so we are going to implement our own persistent cookie solution using a custom authentication module and a post authentication processing hook. We need these hooks, because:

  • The authentication module verifies that the value of the persistent cookie is correct and figures out the username that the session should be created with.
  • The post authentication processing class makes sure that when an authentication attempt was successful a persistent cookie is created. Also it will clear the persistent cookie, when the user logs out.

In order to demonstrate this implementation, I’ve created a sample project on Github, so it’s easier to explain, the full source is available at:
https://github.com/aldaris/openam-extensions/tree/master/rememberme-auth-module
You most likely want to open up the source files as I’m going through them in order to see what I’m referring to. ;)

Let’s start with the Post Authentication Processing (PAP) class, as that is the one that actually creates the persistent cookie. In the PAP onLoginSuccess method, I’m checking first whether the request is available (for REST/ClientSDK authentications it might not be!), then I try to retrieve the “pCookie” cookie from the request. If the cookie is not present in the request, then I start to create a string, that holds the following informations:

  • username – good to know who the user actually is
  • realm – in which realm did the user actually authenticate (to prevent accepting persistence cookies created for users in other realms)
  • current time – the current timestamp to make the content a bit more dynamic, and also it gives a mean to make sure that an expired cookie cannot be used to reauthenticate

After constructing such a cookie (separator is ‘%’), I encrypt the whole content using OpenAM’s symmetric key and create the cookie for all the configured domains. The created cookie will follow the cookie security settings, so if you’ve enabled Secure/HttpOnly cookies, then the created cookie will adhere these settings as well.
In the onLogout method of the PAP I make sure that the persistent cookie gets cleared, so this way logged out users will truly get logged out.

On the other hand the authentication module’s job is to figure out whether the incoming request contains an already existing “pCookie”, and if yes, whether the value is valid. In order to do that, again, we check whether the request is available, then try to retrieve the cookie. If there is no cookie, then there is nothing to talk about, otherwise we will decrypt the cookie value using OpenAM’s symmetric key.
The decrypted value then will be tokenized based on the “%” character, then we first check whether the current realm matches with the cookie’s realm. If yes, then we check for the validity interval and the stored timestamp. If things don’t add up, then this is still a failed authentication attempt. However if everything is alright, then we can safely say that the user is authenticated, and the username is coming from the decrypted content.
In case there was some issue with the cookie, then we will just simply remove the “pCookie”, so hopefully we won’t run into it again.

Limitations

There are a couple of limitations with this example module though:

  • when the PAP is part of the authentication process, it will always create a persistent cookie for every single user (but only when the cookie don’t already exist).
  • the validity interval and the cookie name is hardcoded, moreover every single realm will use the same cookie, that can be a problem in certain deployments.

If you are looking for installation details, then check out the Github project README ;)

Implementing remember me functionality – part 1

It’s quite common requirement to have long running sessions within OpenAM, so I’m going to try to enumerate the different ways to achieve “persistent” sessions and provide some comparison.
There is two main way to achieve long running sessions:

  • Using built-in functionality
  • Implementing custom authentication module + post processing class

Today we are only going to deal with the built-in persistent cookie features, the next article will describe the more user-specific solution.

Setting Expires attribute on session cookie

By default OpenAM issues session cookies without Expires attribute, meaning the cookie is only stored until the browser is stopped/restarted. This solution is especially handy if you have large session timeout values set, because in any other cases your session cookie would be saved in the browser for a good while, but the underlying session could have already time out.
Now there are two ways to enable this mode:

  • Enable persistent cookies globally for all sessions:
    Go to Configuration -> Servers and Sites -> Default Server Config -> Advanced tab, then add the following properties:

    openam.session.persist_am_cookie=true
    com.iplanet.am.cookie.timeToLive=100
    
  • After doing so you will probably need to restart the container for the changes to take effect. Any new sessions created afterwards will set the Expires attribute on the session cookie.
    When using DAS you need to set these properties in the DAS configuration file instead.

  • Allow to have persistent sessions on-demand:
    Go to Configuration -> Servers and Sites -> Default Server Config -> Advanced tab, then add the following properties:

    openam.session.allow_persist_am_cookie=true
    com.iplanet.am.cookie.timeToLive=100
    

    In this case to get a persistent cookie you need to include the openam.session.persist_am_cookie=true parameter on the Login URL (so you can’t actually put this on the Login form unfortunately)

  • When using DAS you need to set these properties in the DAS configuration file instead.

NOTE: the timeToLive value is in minutes.
NOTE 2: these properties only fully work on the core servers if you have OPENAM-1280 integrated, but if you use DAS these should work just fine.
NOTE 3: the timeToLive value only tells how long the cookie should be stored by the browser, it has absolutely NO influence to the actual session timeout intervals.

Creating a long living non-session cookie

When using this option OpenAM will create a second “DProPCookie” cookie next to the iPlanetDirectoryPro cookie with an encrypted content. This encrypted cookie stores a set of informations that are necessary to “recreate” a session when a user goes to OpenAM without a valid session cookie, but with a valid persistent cookie. OpenAM then decrypts the cookie and based on the stored information, it will create a new session.
In order to enable this persistent cookie mode you need to go to Access Control -> realm -> Authentication -> All Core Settings page and do the followings:

  • Enable the “Persistent Cookie Mode” option
  • Set “Persistent Cookie Maximum Time” option

After this when you include the “iPSPCookie=Yes” parameter on the login URL, or you have an actual checkbox on your Login.jsp submitting the exact same parameter during login, OpenAM will issue the DProPCookie persistent cookie with the configured timeout.
This method does not seem to be implemented for DAS.
NOTE: OpenAM will delete the persistent cookie as well, when the user logs out.
NOTE 2: due to a possible bug (OPENAM-1777) OpenAM may not invoke Post Authentication Processing classes when it recreates the user session.

Conclusion

As you probably noticed, the Expires on session cookie method is only really useful when you have large timeouts, also it is a global option, meaning that once you enable it, it will be set for all the different realms.
On the other hand DProPCookie is configurable per realm, but it does not seem to work when using DAS.

That’s all folks, this is how the built-in persistent cookies work, if these do not seem to match your requirements, then stay tuned for the next post.