Creating a seamless session experience with OIDC
REST APIs are everywhere now and for good reason. Say you are a savvy service provider, and you don’t want to only offer a web application that requires direct user interaction. You know that business value comes from integration and partnerships, so you publish your services as a REST API. By providing this API, you enable your services to be used by many different applications, expanding your reach, and increasing your revenue.
Security is critical for every form of service you offer, including your REST API. At the same time, you know that for an API to be successful, it has to be easy to work with, so you balance security and usability by relying on standards, specifically, OAuth 2.0. This is a great choice, because a broad ecosystem is available to support the publication and consumption of OAuth 2.0-based APIs (see "The ForgeRock Platform" for details). There are many options to choose from in the market for client software; these are used to request access tokens and then supply them as part of the REST API calls. If you are unsure how OAuth 2.0, works, take a look at my past article “The OAuth2 Apartment Building.“
Your own web applications are still very important—direct user interaction with your services is absolutely needed. This presents you with a choice; build your own web applications as OAuth 2.0-based clients that use your REST APIs as third-parties use them, or build something proprietary which does not use those APIs. It may initially be easier to build an application without relying on the REST API, as designing a complete REST API that is suitable for all use cases can be hard.
The problem with this option is that you are now faced with maintaining multiple points of entry into your system—the REST API used by third-parties and your proprietary web application. Inevitably, one of those points will be less robust than the other, and you will have essentially doubled your work to maintain them. The best approach for the long term is to build your web applications using the same OAuth 2.0-based REST APIs that third-parties use.
Presenting a single sign-on experience for your users is as important as ever. When users log in anywhere within your platform, they have a reasonable expectation that the session they started will work everywhere they browse within your platform. Likewise, they have an expectation that whenever they log out from anywhere within your platform, their session will be terminated everywhere within your platform. Since your web application is being built on top of an OAuth 2.0-based REST API, you need to find a way to provide this kind of seamless session experience in that environment.
The Solution: OIDC
OpenID Connect (OIDC) is the fundamental technique required to achieve single sign-on (SSO) in this context. OIDC is a standard way to let an OpenID Provider (OP) handle authentication for a user on behalf of a relying party (RP) application. For a high-level overview of how OIDC works, see my previous article “The OpenID Connect Neighborhood.“ With OIDC, you can obtain the two things you need: a valid OAuth 2.0 access token to submit to the REST API, and information about the user that is currently logged in. The RP needs the authenticated user’s identity details and gets them in the form of an id token from the OP. By virtue of being an extension to OAuth2, logging in with OIDC will also allow the RP to obtain the access token. ForgeRock Access Management is built to operate as an OpenID Provider.
Initially logging in is a well-established part of the OIDC process. The first time a user needs to log in to any of your web applications, they will be redirected to the OP to do so. The OP is responsible for authenticating them, and then maintaining session details about that authentication. The OP will set a cookie in the user’s browser that identifies this session that is only readable by the OP. The OP will then redirect the user back to the RP so it can fetch the tokens it needs.
Keeping tokens current is the main challenge that your web applications need to solve for SSO. By current, I mean that they need to stay in sync with the session established within the OP. When that session is valid, then the RP tokens should be valid. When that session ends, the tokens used by the RP should be revoked. The problem is that this session identifier lives in the user’s browser under the OP domain and is completely unavailable to the RP. This makes monitoring for changes to that session difficult for the RP. Fortunately, there is a workaround that browsers can use to overcome these limitations.
Hidden iframes within a web application provide a very powerful means of monitoring for OP session status. These are basically non-interactive browser contexts that can leverage redirection to pass messages between the OP and the RP. By embedding an iframe within your web application, you can periodically use it to call the OP’s authorization endpoint with the
prompt=none URL parameter. When the iframe loads this URL, it includes the OP session cookie as part of the request. This is how the OP recognizes the session. The authorization endpoint will always respond by redirecting the frame to your specified
redirect_uri location, along with additional parameters that provide details about the state of the user’s session. If the session is still valid, the parameters let you determine the currently logged in user from the id token details. If the session has expired, then the response will include error messages such as interaction_required.When the iframe has these messages available, it can pass information about them back to the parent web application frame using the browser’s postMessage API.
Deciding when to check the session at the OP using the hidden iframe is a topic of debate. The draft specification for OpenID Connect Session Management states:
[I]t is possible to repeat the Authentication Request with prompt=none. However, this causes network traffic and this is problematic on the mobile devices that are becoming increasingly popular.
prompt=none technique. The theory here is that polling for cookie value changes avoids the cost of network traffic, which on mobile devices is especially concerning. While that is a laudable goal in theory, there are several practical problems with this approach.
Another problem is simply adoption. The value to compare against when monitoring for changes in the OP frame is an extra return parameter from the authorization response (called session_state). Most OIDC RP libraries do not recognize this parameter, and would have to be altered to support it.
The most critical problem with this specification is mobile traffic. This was first expressed in August 2012 as part of draft 08, back when mobile networks were merely "increasingly popular". Now that they have become much more robust and ubiquitous, the relative cost of a periodic network request to check session status at the OP is much lower, whereas the cost of the specification in terms of complexity remains high. The simplest solution would be to just use a setInterval call to reinitiate the authentication request every few seconds (or minutes, if so desired). This would happen regardless of user interaction in the RP, and it would be triggered at whatever frequency you configure. This approach, while easy, does have the downside of causing more load on the system, both in terms of network overhead and work for the OP.
Another option is to use an event-based strategy; basically, only check the session status when the user does something within the RP. This could be page transitions, network requests, key presses or mouse clicks. Whichever events you choose to monitor, you will want to be sure not to issue more than one session check request within a particular window of time (say, every few seconds at the minimum). This is probably the best option, since it won’t overwhelm the network or the OP, and yet still provides very quick feedback to the user when their session expires.
Handling an expired session within the RP is pretty straightforward. Just revoke the access token using the mechanism described in the OAuth 2.0 Token Revocation specification and call the end session endpoint with the id token. Afterwards, delete the tokens from your RP memory and redirect the user to a page that they can access without credentials (this might end up just being the OP login page).
The general pattern of requests for this process looks like this:
The ForgeRock Identity Platform
You can use the ForgeRock Identity Platform to build the infrastructure needed for this environment. ForgeRock Access Management operates as the OP. It offers strong authentication, maintains sessions, and issues both id and access tokens. ForgeRock Identity Gateway can protect your REST APIs by acting as the Resource Server (RS). It does this by intercepting requests to your APIs; before forwarding the request along, it will read the token from the request and perform token introspection via a call to AM. If AM indicates that the access token has the necessary scopes, the request will be forwarded to the underlying REST API.
The Ideal Relying Party
This approach for session management could be used for any type of OIDC RP that uses the browser as a user agent. The only real technical requirement is it can embed a non-interactive browser context (such as a hidden iframe) with access to the OP session cookie. However, when your web application is designed to work exclusively with your OAuth 2.0 REST APIs as the backend, you will probably find that the best choice for building your RP is using a pure frontend technology such as a Single-Page Application (SPA). This is because doing so lets you avoid running an additional application server that is only operating as a proxy to your REST API.
Public clients have two types of grants available to implement— Authorization Code and Implicit. Based on the descriptions in the specification, it may appear that a SPA should be built using the implicit grant. However, industry trends and best current practices that have emerged since the initial spec was written suggest that this is not the best choice after all. Instead, use of the authorization code grant as a public client is considered more secure (most notably by avoiding presence of the access token in browser URL history).
PKCE is an extension to OAuth 2.0 that is designed specifically to make the authorization code grant even more secure for public clients. Essentially, PKCE prevents a malicious third-party from using a public client’s authorization code to obtain an access token. While it should be very difficult to intercept an authorization code served over HTTPS, using PKCE provides a valuable additional layer of protection.
To summarize, the ideal relying party for your REST APIs is a single-page app that uses PKCE to obtain the tokens and periodically checks that the session at the OP is still valid.
ForgeRock has published two open source libraries to help make building this sort of RP as easy as possible:
AppAuth Helper is a wrapper around the AppAuth-JS library that makes it easy to use in the context of a single-page app. AppAuth-JS is designed to support PKCE, which makes it a good choice for obtaining tokens in a public client. This helper automates the usage of AppAuth-JS for token acquisition and renewal, providing a very seamless login and token management experience.
OIDC Session Check is a library designed to make it easy to add the session-checking hidden iframe into your web application RP. It can be added into any web-based app (SPA or not). The only requirement is that you supply the username of the user that is currently logged in. Then you only have to decide when to check the session, based on a regular poll and/or based on various events.