How to build an SSO client for your REST APIs with OIDC

The Problem

REST APIs are everywhere now, and for good reason. Say you are a savvy service provider – 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, and 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 making more money as a result.

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. You balance security and usability by relying on standards – specifically, OAuth2. This is a great choice, because there is a broad ecosystem available to support the publication and consumption of OAuth2-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 OAuth2 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 OAuth2-based clients that use your REST APIs similar to how third-parties would 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; designing a complete REST API that is suitable for all use-cases can be hard. The problem with this option is that now you are 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 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 OAuth2-based REST APIs that third-parties use.

Presenting a single sign-on experience for your users is as important as ever. When they login anywhere within your platform, they have a reasonable expectation that the session they started will be good everywhere they browse within your platform. Likewise, they have an expectation that whenever they logout 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 OAuth2-based REST API, you will need to find a way to provide this kind of seamless session experience in that environment.

The Solution

OpenID Connect (OIDC) is the fundamental technique required to achieve single sign-on in this context. OIDC is a standard means to allow an “OpenID Provider” (OP) to handle authentication for a user on behalf of a “relying party” (RP) application (for a high-level overview of how OIDC works, take a look at my previous article “The OpenID Connect Neighborhood“).  Using OIDC, you can obtain the two things you need: a valid OAuth2 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 login 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, which is only readable by the OP. The OP will then redirect the user back to the RP so that it can fetch the tokens it needs.

Keeping tokens current is the main challenge that your web applications need to solve for single sign-on. 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 trick 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 which 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 will include the OP session cookie as part of the request; this is how the OP will be able to recognize the session. The authorization endpoint will always respond by redirecting the frame to your specified “redirect_uri” location, along with additional parameters which tell you details about the state of the user’s session. If the session is still valid, the parameters will allow you to 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.

The specification then goes on to describe a technique involving multiple iframes which exist in both the RP and the OP domains. The spec suggests that the OP-based iframe should use JavaScript to poll for changes to a cookie value that represents the current state of the session on the OP; upon a change detection, only then use the 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.

One problem is the simple fact that many security practices suggest setting session cookies to be “httpOnly“; this makes them impossible to read from JavaScript, which makes them impossible to use in the way described by the spec. This issue could be overcome if the OP sets an additional cookie that is not marked as httpOnly and is intended exclusively for the use in the OP iframe. This cookie value would have to be maintained so that it changed every time the main session value changed, but also must not give away anything about the main session value that might undermine the security provided by the httpOnly flag.

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 so would have to be altered to support it.

The most critical problem with this specification is the concern it has regarding mobile traffic; this was first expressed in August 2012 as part of draft 08, back when mobile networks were merely “increasingly popular”. Now 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 re-initiate 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. Whatever 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, simply 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).

Sequence Diagram

The general pattern of requests for this process looks like this:

The ForgeRock Platform

You can use the ForgeRock Identity Platform to build the infrastructure needed for this environment. ForgeRock Access Management operates as the OpenID Provider (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.

An example of this configuration which uses ForgeRock Identity Management’s REST API as the API being protected is available to try out, here: Platform OAuth2 Sample.

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 OAuth2 REST APIs as the back-end, you will probably find that the best choice for building your RP is using a pure front-end technology such as a Single-Page Application (i.e. “SPA”). This is because doing so will allow you to avoid having to run an additional application server that is only operating as a proxy to your REST API.

The SPA pattern is a popular modern technique for building web applications, and it does not require the use of an application server. Instead, SPAs execute entirely within a web browser using JavaScript. This JavaScript application code is completely public – any user familiar with how web pages operate can easily read all of it. As a result, these applications cannot have anything sensitive (like passwords) included within their code. This means that SPAs are classified as “public” clients.

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 OAuth2 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 will be a single-page app that uses PKCE to obtain the tokens and periodically checks that the session at the OP is still valid.

The Code

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.

For an example showing how to use these two together, take a look at this OAuth2 client that was designed to work with the ForgeRock Platform OAuth2 Sample: IDM 6.0 End-User UI with AppAuth

The OpenID Connect Neighborhood

Understand OpenID Connect by analogy and learn how it relates to OAuth2.

In my last article, I described the benefits of living in the OAuth2 apartment building. Something I didn’t mention is that the neighborhood my building is in is really unique, too. There are several other buildings like mine (OAuth2-enabled) in the area, which has turned out to be very convenient for the local businesses and for the residents. I’ll give you an example of how this arrangement has benefited us all.

I was walking through the lobby recently and saw another advertisement on the community bulletin board, this time for “Ripley’s Gym”. They were offering a special discount for all new members, exclusively for people who live in one of the OAuth2-style apartments.

I knew that it was long past time to start exercising regularly, so I decided to call them up. This gym was very eager to sign up new members – competition is quite stiff for these sorts of businesses. To make it as easy as possible to get me started, they were willing to send Ripley himself over to meet me in the building lobby only a few minutes after I called.

Ripley had previously made a partnership agreement with the management of each of the neighborhood buildings. This partnership meant that there was a smooth process available for him to get the necessary details about each tenant when they signed up. It also meant that he knew that the people signing up were actually tenants in the building.

When he showed up to meet me, I saw the process first hand. First, I met Amy (remember her from my other article?) at the front-desk, along with Ripley. I showed Amy my driver’s license, and Ripley showed her his business card. Amy then had me sign a quick release document which stated that I was okay with Ripley having some of my personal information: name, age, gender, mailing address, phone number, and email address. Given that this meant I didn’t have to fill out a tedious membership form, I was happy to sign Amy’s release document.

Amy then printed out a small ticket for Ripley which had all of that information on it. One really cool thing about this ticket is that Ripley was able to instantly scan it and use the information within to register me at the gym. This was possible because the information on the ticket was standardized – every single OAuth2-style apartment building in the neighborhood is able to print off these same types of tickets with the same standard details about the tenant. Each ticket is also watermarked with the building address that it came from, which means that only legitimate tickets will be recognized.

Now that I was registered with Ripley’s Gym, there was another neat option available to me as a tenant from one of these buildings. Every time I wanted to go to the gym, I just needed to grab a ticket from the apartment front-desk on my way out the door (they can print these very quickly). I can use this ticket when I get to the gym as a means of identification, and also as a simple way to keep my customer information current within the gym’s files. You see, if my phone number or email address (or anything else about me) changes over the course of my time as a gym customer, I won’t have to remember to keep them updated – these details are stored on the tickets, so every time I visit the gym they have the latest information.

Nothing about this ticket-issuing process is unique to Ripley’s Gym. Any of the local businesses that are willing to follow this process are welcome to do so, and many of them do. Likewise, when a new building is opened the details about how businesses can start serving its tenants are published in the lobby. These details always follow a well-known template, making it very easy for businesses to serve those tenants.

Working with the local businesses like this has become a way of life for the people in our neighborhood, which has made things easier for everyone. We no longer have to deal with repeatedly filling out customer sign-up forms or updating our personal information, and we no longer need to worry about having to carry keys for each of the members-only services we want to use. Instead we just get a quick ticket from the front desk and we are on our way. Honestly, I tend to avoid businesses that don’t follow this process – it’s just not worth the hassle.

You might notice there is some similarity between the experience I described with “Clint’s Dog-Walking Service” and “Ripley’s Gym”. In both cases, I met the owner at the front-desk with Amy, and in both cases I signed a form granting my consent for Amy to offer something to the business owner on my behalf. In Clint’s case, he was getting a key card to be able to open some of my apartment doors; in Ripley’s case, he was getting a ticket that had some of my personal information on it. The difference between key cards and tickets may sound obvious, but it is worth highlighting some of those differences:

  • Information stored within the key card is hidden from the person receiving it; information stored within the ticket is intended to be read by the person receiving it.
  • The key card can be used to open apartment doors; the ticket does not imply any such authorization.
  • The ticket can be used as a standard and secure means of identification; using a key card as a means of proving your identity would be awkward, imprecise and would vary from building to building.

Despite these different uses, it is quite convenient to have a similar process for both. Amy does a great job of facilitating these interactions; she has all of the tenant information, consent forms, ticket printers and key card machines that are necessary to make everything work quickly and easily. Also, in more and more cases businesses want a ticket and a key card at the same time, since this makes it easier for them to keep track of whose key card is whose. Given that, having Amy handle it all only makes sense.

No surprises here: this isn’t really my neighborhood. It’s OpenID Connect. This analogy explains why OpenID Connect was created and how it relates to OAuth2.

As you can tell from the analogy, OpenID Connect (OIDC) is designed to solve a very different problem than OAuth2. OIDC is designed to help identify people consistently, regardless of the “identity provider” which is being used.  OAuth2 is designed to request limited access to people’s resources, and is highly specific to the resources in question. As I mentioned above, though these uses are different they are often complementary.

In my analogy, Ripley’s Gym is known as a “relying party.” A relying party is any entity that uses (or “relies” upon) an external identity provider in order to establish the identity of its users.

Each of the “OAuth2-style apartment buildings” is an example of an identity provider. The main thing that a relying party needs from an identity provider is an “id token“. These are the “tickets” that I described – they are in a standard format (known as a JWT) that contain various details about the user and they are cryptographically signed by the provider (conceptually, this is similar to a watermark). These id tokens are suitable for registering users (as Ripley did when I first signed up for the gym) as well as logging users in (as I described using them to get inside the gym).

Given that one of the main points of OIDC is interoperability between providers, all OIDC identity providers are required to publish a “discovery document” which tells the relying parties exactly how to work with it. This is that “well-known” template I mentioned that the neighborhood apartment buildings had to have available. The discovery document includes details like where to go in order to authorize users, where to get tokens, where to get information about users, etc….

You can use ForgeRock Access Management (AM) to implement the role of an OIDC Identity Provider. There are many libraries certified by the OpenID Foundation which can help you build a relying party. If your relying party software can’t be made to work as an RP directly, you should also consider using the filter provided by ForgeRock Identity Gateway to build this feature.

By building your software services using this pattern, you will foster a convenient and secure environment for your users and your business partners. Go forth and be part of the OpenID Connect neighborhood!

The OAuth2 Apartment Building

Understand core OAuth2 concepts by analogy and learn how the various ForgeRock Identity Platform components relate to OAuth2.

I live in a modern apartment building. It’s a very nice place – pet friendly, upscale furnishings, prime location. One of the best parts about my apartment building, though, is that it has electronic locks available for all the doors. This is pretty similar to most new hotels; each door opens only by using a key card that was issued by the front-desk. The thing that sets it apart from hotels is that it isn’t just the main apartment door that is opened by a key card – each of the inside doors is too. Every internal door in the entire building will only open for you if your key card is authorized for it.

Surprising thing to consider the best feature, right? You might think having doors like this is actually inconvenient and unnecessary – what value is there to having such a locked-down home? What is wrong with just using a plain-old key to open the front door? Let me explain some of the advantages that this door setup offers.

The other day I was walking through the lobby and I noticed that there was an item on the community bulletin board – an advertisement for “Clint’s Dog-Walking Service”. For a small fee, this company offered to make regular stops up to my apartment to pick up my dog (Fido) and take him for walks, whether I’m at home or not. I felt bad for leaving my pooch at home all day while I was at work, so this sounded like a great deal! I called them up for a trial.

This was a small company, and so they sent Clint himself to meet me in the lobby. I filled out the paperwork for my initial trial and then we walked up to the front-desk for Clint to get his key card to my place. The attendant working the desk was named Amy. The first thing Amy needed to see from me was my driver’s license – she wanted to make sure I really was a tenant in the building and also find out which apartment was mine. Next, as is building policy for all key cards issued to non-tenants, Amy asked Clint for his company information along with which doors of mine he would like to be able to open – it’s important that these details are on file with building management, for liability purposes.

Once she was able to verify that Clint’s company was properly registered, she had me look over a form listing the doors that Clint was asking permission to access. For his dog-walking service, he only needed to open my front door and the “dog room” (like I said, this is a very pet-friendly apartment building). I certainly didn’t want him to be able to open my bedroom or office door – he has no business in there, especially when I’m not at home! Fortunately he was only asking for the doors he needed, so I signed Amy’s form. She filed this away for record-keeping and then produced a brand-new key card, which she then handed over to Clint. This key card was only good for a month; this is another building policy designed to reduce the risk of it getting lost or stolen.

Clint then started his regular service walking my dog. Every time he opened one of my doors, the software managing the door logged his entries. I had peace of mind knowing that he could enter my apartment but only to the areas I had approved. I was very happy with the convenience of the whole thing and confident that my privacy was secure.

After the first month, his card expired and so he stopped by the front-desk for a new one. Amy checked to make sure that he was still allowed access; since I was apparently happy with the service at this point (as far as Amy could tell), there was no reason to refuse his request for a new card. Service continued uninterrupted.

Sadly, I eventually decided that Clint’s service wasn’t really worth the money he was asking. In addition, I was working from home more so I didn’t leave my dog alone as often. I contacted Clint and told him I wanted to cancel my account. I also called up Amy at the front-desk and told her that I would like to deactivate Clint’s current key card, and also prevent him from getting any more cards in the future.

This is why my apartment building really is awesome – if I had an old-school physical key and lock on my door, I would have had to make a copy of my key for Clint. In that case, I would have had no idea when he came and left, which rooms he went into, or if he made any other copies of that key for anyone else. When the time came for me to cancel his service, I would have had to take his word for it that he hadn’t made any additional copies of my key when I asked for it back. To have complete peace of mind that he couldn’t come back in without my approval I would have had to change the locks on my door (and if I had changed the locks, anyone else that I had given a key would need to get a new one). These are major problems! I would have never accepted that much hassle and risk if I had been using an old door lock. As it turns out, Clint’s business was really only possible in an environment where I can control these factors.

This is not really my apartment building, it’s OAuth2. This analogy explains exactly why OAuth2 was created and covers the basic way it works.

Before OAuth2, when you needed to give software services access to your account to do things on your behalf you had to give that service your username and password. This is exactly like the physical door key – there is no way to tell whether the agent accessing your data is a third party doing so on your behalf rather than you doing it yourself. As with the physical key, they would have complete access to everything in your account (similar to how a person with a single physical key to the main apartment door would have access to everything inside). If you want to cancel your service, you would need to change your password to be sure they wouldn’t be able to use it again without your permission.

OAuth2 solves this in the same way that my fictitious apartment building solves the physical key problem.

The apartment you wish to visit is an OAuth2 “resource“. Every apartment has an owner; in OAuth2, every resource has a “resource owner” that is authorized to grant access to it. These are the users in your systems; their data are the resources. ForgeRock Identity Management defines an API for operating on user data that may be exposed in this context.

Just how my apartment building replaced physical keys with electronic key cards, in OAuth2 usernames and passwords have been replaced with “access tokens“.

Similar to opening an apartment door with a key card, you request resources using an access token. Likewise, in the way that you would expect a key card to only be able to open certain doors, access tokens are only usable for specific things. In OAuth2, the particular things an access token is capable of being used for are called “scopes“. In my analogy, the doors within my apartment are all coded to require a specific scope (such as “main door”, “dog room”, “bedroom”, “office”, etc…). Any access token presented to one of them which does not have the required scope will fail to open it.

Clint is an example of an OAuth2 “client“. A client is any entity which has been given an access token. Clients vary based on their relationship to the other parties and the ways in which they are able to get an access token. Regardless of these details, all clients essentially behave the same after they have a token – they use it to request protected resources. Clients are the software applications that you bring into your systems to provide benefits to your users. For software without native OAuth2 client capabilites, ForgeRock Identity Gateway offers an easy way to operate as an OAuth2 client.

The building front-desk and associated door tracking software is the OAuth2 “authorization server” (or AS). It is chiefly responsible for issuing access tokens to clients. For that to be possible, some of the same steps that were taken in my story are also required in OAuth2. For example, by presenting my driver’s license I was able to “authenticate” to the front-desk; OAuth2 authorization servers also require an authentication step (usually a username and password prompt but often other prompts too). After Amy at the front-desk checked my ID, she then looked up the details for Clint’s company. This is part of OAuth2 as well – the client needs to be registered with the AS before any tokens are issued to it. The client is responsible for telling the AS which scopes it would like, similar to the way Clint told Amy which doors he wanted to be able to open. The AS shows the resource owner a list of those scopes, as a means of obtaining informed consent regarding what the client will be able to do with their token. If the resource owner approves, then the AS returns the access token to the client. ForgeRock Access Management implements the role of the authorization server.

Every time the client uses its token to request a protected resource, there is a check made by the software protecting the resource (in OAuth2 terms, this software is called a “resource server“) to verify that the token is legitimate, not expired, and has the necessary scopes for the request. This is called “token introspection“. Usually, token introspection involves a call to the AS that issued the token. In the same way that the door checks with the building management software when a key card is presented, this is something that happens without the client’s involvement. The client only sees if the request is granted; the door either opens or it doesn’t. ForgeRock Identity Gateway offers an easy solution to act as a resource server, with several means of token introspection available to choose from.

Access tokens expire in the same way that the key cards were described. Depending on the agreement made between the client, the resource owner, and the AS there may be a way for the client to obtain new access tokens without having to ask the resource owner every time it expires. This is just like the way Clint was able to get a new key card from Amy after his first expired. In OAuth2, this process involves the “refresh token“. The refresh token is only usable to get new access tokens; it’s not usable directly to access protected resources. The point of this token is to try to limit the risk of lost or stolen access tokens. Both refresh tokens and access tokens can be revoked by the resource owner on the AS, in the same way that I described calling Amy when I wanted to cancel my service with Clint. Revoked access tokens will be detected during token introspection, which will prevent their continued use.

OAuth2 enables new businesses and practices to thrive in a trusted environment where it would be impossible otherwise. By establishing this consent relationship between the clients and the resource owners, people have the tools necessary to maintain their privacy and security to the degree they demand. The ForgeRock Identity Platform is the best means to offer that relationship – go forth and build your own OAuth2 apartment building!

Special thanks to Aaron Parecki for the basic “access token as hotel key card” analogy, which I heard him mention in a user group presentation similar to the one he gives here: Using OAuth2 and OpenID Connect in your Applications.

Update: Read the follow-up which describes how the OAuth2 Apartment Building relates to the OpenID Connect Neighborhood

Using IG to Protect IDM For Secure and Standards-Based Integration

ForgeRock Identity Management (IDM) has a rich set of REST APIs capable of performing many actions; this is one of the great values that IDM has to offer. However, with such a broad set of APIs available out of the box, it is reasonable to want to limit which of those REST APIs are directly available to the public. The most common way to enforce that limit is to use an API gateway to act as a reverse proxy. In this way, only the requests which the gateway has been configured to allow will be sent to IDM.

In addition to offering a filter, API gateways can offer many powerful options for authentication and authorization which are not available directly within IDM. One important example of this need is for OAuth2 clients to be able to request IDM REST APIs using an access token that was obtained from ForgeRock Access Management (AM). In this way, the IDM endpoints would be able to be treated as standard OAuth2 resource server endpoints; the gateway can validate the presence of the access token and introspect it to ensure that there are appropriate scopes for the request. Being able to rely on an access token as the means by which clients work with the IDM REST API is often easier, more secure, and more inter-operable than other means available.

In this article I’m going to focus on how you could configure ForgeRock Identity Gateway (IG) to accomplish both of these goals. You will be able to use this configuration as a pattern for deploying IDM functionality in a secure and standards-based way, making it easy to integrate with your own applications. Some basic knowledge about installation and typical use for each product is assumed; please review the product documentation if you are unfamiliar with either product.

Securing the connection between IDM and IG

When IG is responsible for securing the requests to IDM, it is important to configure IDM so that it will only accept connections which originate from IG. One way to accomplish this is to configure your network topology and firewall rules to prevent anything besides IG from being able to make a connection; doing so will be specific to your environment, so I won’t cover how to do that here; ask your network administrator for help. In addition to the network level security, you should also secure the connection with an encrypted channel using client SSL certificates. Both methods used together will provide the most robust security.

Configuring IDM to accept connections from IG

Since IG will be handling the direct end-user authentication, IDM only needs to handle the authentication of the requests originating from IG. As they are separate processes, there must be a standard HTTPS interface between them (IG proxing the client requests to the IDM server). The main security challenge is to ensure that only IG is able to make these direct HTTPS requests to IDM. Essentially, IDM needs a way to first authenticate IG itself so that it can then operate on behalf of the actual user. There are a few details that need to considered in order to do this.

Static Users

First is the simple fact that IG is not a “user” in the sense that IDM normally expects – it is more of a service, and as such will probably not have a corresponding record available on the IDM router (in the way that users normally do).  Because authentication in IDM requires a route for every authenticated subject, we have to find a way to supply one. One simple way to do this is to create a custom endpoint that provides a sort of pseudo-route. For example, you can create a file named “conf/endpoint-staticuser.json” with this content:

{
    "type" : "text/javascript",
    "context" : "endpoint/static/user/*",
    "source" : "request.method === 'query' ? ([{_id: request.additionalParameters._id}]) : ({_id: context.security.authenticationId})"
}

This handy endpoint is designed to work with the authentication service. It simply reflects back the id provided (either as a query parameter or from the security context). Using this, we can define authentication modules for “users” which don’t have any other router entry, such as the case with IG.

Client Certificate Authentication

The next step is to actually configure the authentication module that IG will be using. In this case, we are going use the “CLIENT_CERT” authentication module, following a similar process to the one described in “Configuring Client Certification Authentication“. Example openssl commands and more background detail can be found there.

This module requires that the “client” (in this case, IG) has a particular SSL certificate that it is able to present to IDM when it connects via HTTPS. Using our above “static/user” endpoint, here’s the most basic example JSON configuration showing how this could be done:

{
    "name": "CLIENT_CERT",
    "properties": {
        "queryOnResource": "endpoint/static/user",
        "defaultUserRoles": [
            "openidm-cert"
        ],
        "allowedAuthenticationIdPatterns": [
            "CN=ig, O=forgerock"
        ]
    },
    "enabled": true
}

This declaration authenticates any request which has supplied a client certificate that meets two conditions:

  1. It is trusted in terms of SSL (either directly imported in the IDM truststore or signed by a CA which is within the IDM truststore).
  2. It has a “subject” which matches one of the patterns listed under “allowedAuthenticationIdPatterns”. In this example, the subject needs to exactly match “CN=ig, O=forgerock”.

If both of those conditions are met, then the request will be authenticated, and it will be recognized as a “static/user” type of entity with the “openidm-cert” role. The intent behind this configuration is that requests from IG are recognized within this context.

Run As

The final configuration necessary within IDM is to allow IG to make requests on behalf of the user it has authenticated. In IDM 6.0, there is a new feature available for every authentication module which supports this – “RunAs Authentication“. This feature allows privileged clients to supply an “X-OpenIDM-RunAs” header in order to specify the name of the user they would like to operate on behalf of. This is obviously a highly-sensitive type of operation – only the most trusted clients (such as IG) should be allowed to operate under the guise of other users. You will need to configure IDM in order to allow IG to do this. For example, you can expand the CLIENT_CERT authentication module configuration like so (added values in italics):

{
    "name": "CLIENT_CERT",
    "properties": {
        "queryOnResource": "endpoint/static/user",
        "defaultUserRoles": [
            "openidm-cert"
        ],
        "allowedAuthenticationIdPatterns": [
            "CN=ig, O=forgerock"
        ],
        "runAsProperties": {
            "adminRoles": [
                "openidm-cert"
            ],
            "disallowedRunAsRoles": [ ],
            "queryOnResource": "managed/user",
            "propertyMapping": {
                "authenticationId" : "userName",
                "userRoles": "authzRoles"
            },
            "defaultUserRoles" : [
                "openidm-authorized"
            ]
        }
    },
    "enabled": true
}

By using this configuration, any authenticated request which comes from IG should include an “X-OpenIDM-RunAs” header that identifies the “userName” value for the associated “managed/user” record. IG will be the only client capable of making this sort of request, because it is the only entity which has the private key associated with the trusted certificate.

Configuring IG to accept connections for IDM

Now that IDM is prepared to accept connections, IG needs to be configured to make them correctly.

Trusting IDM

Review the chapter in the IG configuration guide called “Configuring IG for HTTPS (client-side)“. If IDM is using a self-signed certificate, you will need to import that into IG’s truststore as described there.

Supplying a Client Certificate

All requests made by IG to a back-end are via a “ClientHandler“.  By default, there is no special behavior associated with a ClientHandler – it simply acts as a generic HTTP/S client. The best option for us is to define a new ClientHandler on the IG “heap” that is configured to perform client certification authentication. The most important declaration for this use is the “keyManager“; this is how you tell the ClientHandler where to get the keys to use when prompted for client certificates. Here is an example of a configured client that is ready to trust IDM’s certificate and supply its own:

{
    "name": "IDMClient",
    "type": "ClientHandler",
    "config": {
        "hostnameVerifier": "ALLOW_ALL",
        "sslContextAlgorithm": "TLSv1.2",
        "keyManager": {
            "type": "KeyManager",
            "config": {
                "keystore": {
                    "type": "KeyStore",
                    "config": {
                        "url": "file:///var/openig/keystore.jks",
                        "password": "changeit"
                    }
                },
                "password": "changeit"
            }
        },
        "trustManager": {
            "type": "TrustManager",
            "config": {
                "keystore": {
                    "type": "KeyStore",
                    "config": {
                        "url": "file:///var/openig/keystore.jks",
                        "password": "changeit"
                    }
                }
            }
        }
    }
}

In this example, the /var/openig/keystore.jks file contains both the public certificate needed to trust IDM and the private key needed to authenticate IG as a client. Make sure that all IG routes which forward requests to IDM use this ClientHandler.

Including the RunAs Header

Before the request is forwarded to IDM via the IDMClient, it needs to be modified to include the X-OpenIDM-RunAs header. This allows IDM to find the user that is actually making the request, via IG.

There are several ways to modify the request before it is sent to IDM, but all of them involve a filter on the route. The simplest example is to use the “HeaderFilter“, like so:

{
     "type": "HeaderFilter",
     "config": {
         "messageType": "REQUEST",
         "add": {
             "X-OpenIDM-RunAs": [ "${contexts.oauth2.accessToken.info.sub}" ]
         }
     }
}

If you have more complex logic around your user behavior, you might want to use a ScriptableFilter, within which you could set the header with code like so:

String sub = contexts.oauth2.accessToken.info.sub
request.getHeaders().add('X-OpenIDM-RunAs', sub)
return next.handle(context, request)

Operating as an OAuth2 resource server

While there are many potential benefits provided by IG, the main one explored in this article is to operate as an OAuth2 resource server. The core feature is well documented – see both the IG gateway guide and the configuration reference. The main detail to consider in this specific context is about how you want to manage scopes. In order for requests to successfully pass through the OAuth2ResourceServerFilter, the token must have the correct scopes associated with it. What constitutes as a “correct” scope value for any given request is up to you to decide. You will need to consider which IDM endpoints and methods you want to make available and how to express that availability in terms of a scope. For example, if you want to allow OAuth2 clients to be able to make calls to the “/openidm/endpoint/usernotifications” endpoint, you could define a scope called “notifications” and require it with a route like so:

{
    "name": "notificationsRoute",
    "baseURI": "https://idm.example.com:8444",
    "condition": "${matches(request.uri.path, '^/openidm/endpoint/usernotifications')}",
    "handler": {
        "type": "Chain",
        "config": {
            "filters": [
                {
                    "type": "OAuth2ResourceServerFilter",
                    "config": {
                        "scopes": [
                            "notifications"
                        ],
                        "accessTokenResolver": "AccessTokenResolver"
                    }
                },
                {
                     "type": "HeaderFilter",
                     "config": {
                         "messageType": "REQUEST",
                         "add": {
                             "X-OpenIDM-RunAs": [ "${contexts.oauth2.accessToken.info.sub}" ]
                         }
                     }
                }
            ],
            "handler": "IDMClient"
        }
    },
    "heap": [
        {
            "name": "AccessTokenResolver",
            "type": "TokenIntrospectionAccessTokenResolver",
            "config": {
                "endpoint": "https://am.example.com/openam/oauth2/introspect",
                "providerHandler": {
                    "type": "Chain",
                    "config": {
                        "filters": [
                            {
                                "type": "HeaderFilter",
                                "config": {
                                    "messageType": "request",
                                    "add": {
                                        "Authorization": [
                                            "Basic ${encodeBase64('openidmClient:openidmClient')}"
                                        ]
                                    }
                                }
                            }
                        ],
                        "handler": "ClientHandler"
                    }
                }
            }
        },
        {
            "name": "IDMClient",
            "type": "ClientHandler",
            "config": {
                "hostnameVerifier": "ALLOW_ALL",
                "sslContextAlgorithm": "TLSv1.2",
                "keyManager": {
                    "type": "KeyManager",
                    "config": {
                        "keystore": {
                            "type": "KeyStore",
                            "config": {
                                "url": "file:///var/openig/keystore.jks",
                                "password": "changeit"
                            }
                        },
                        "password": "changeit"
                    }
                },
                "trustManager": {
                    "type": "TrustManager",
                    "config": {
                        "keystore": {
                            "type": "KeyStore",
                            "config": {
                                "url": "file:///var/openig/keystore.jks",
                                "password": "changeit"
                            }
                        }
                    }
                }
            }
        }
    ]
}

The details about how to validate the access token are shown here via the “AccessTokenResolver” heap object. There are a lot of legitimate configuration options worth exploring for the “OAuth2ResourceServerFilter” filter; see the reference documentation to see the full range. In this example, it is calling out to a standard OAuth2 token introspection endpoint provided by AM, authenticated with a registered client called “openidmClient”. See the documentation regarding how to configure AM to operate as a token introspection service. Be sure that the client you register for the IG resource server filter has the “am-introspect-all-tokens” scope included within it.

You will want to define similar route entries as this for each scope requirement you want to declare. To reduce the amount of duplicated route configuration, I suggest you move the common parts between each route (such as the IDMClient and AccessTokenResolver heap objects) into the global IG “config.json” file. See more details about route definition in the IG gateway guide section on the subject.

Complete configuration example

There is a full sample configuration available as part of the “ForgeOps” project which follows these patterns. This sample brings up AM configured to operate as an OAuth2 AS, providing token creation and introspection. It configures IG to act as an OAuth2 RS, and configures IDM to accept connections exclusively from IG. By reviewing the “rs” and “idm” sub-folders included within that sample, you should recognize many of the points described in this article. See the included README.md file within that sample for details regarding how to start it up. This sample configuration will be used as the basis for subsequent articles which will discuss how you can use specific OAuth2 client libraries to make requests to IDM endpoints.

Go forth and build your secure, standards-based applications, integrated with the ForgeRock Identity Platform!

Implementing Delegated Administration with the ForgeRock 5.5 Platform

Out of the box in 5.5, IDM (ForgeRock Identity Management) has two types of users – basic end-users and all-powerful administrators. You often need a class of users that fall between these extremes – users which can trigger a password reset action but cannot redefine connector configuration, for example. Another common need is for users to only be allowed to perform actions for a subset of other users – maybe only those within their organization. The typical term for these sorts of users is ‘Delegated Administrators‘ – users who have been granted limited access to perform particular administrative tasks given to them by a more privileged administrator.

There is a way to define new user roles within IDM that have more granular access, but this is fairly limited – you have to write back-end JavaScript code to define what these new roles can do. Also, there is no way to inform the user at runtime about what options they have as a result of these JavaScript-based roles. Instead, you would have to write new UI code which specifically adjusts itself for the each of new roles you define, essentially hard-coding it to match the back-end JavaScript. Depending on the complexity that you need, this can quickly become a big challenge.

The good news is that IDM does not have to do this job by itself – by making use of the other parts of the ForgeRock Identity Platform, you can define very sophisticated authorization logic for each of your users, including the option to delegate administrative tasks to them. The two other products which provide the biggest benefit to IDM for this are AM (ForgeRock Access Management) and IG (ForgeRock Identity Gateway)

AM has a very powerful authorization engine that allows you to declare precise rules which govern the requests made by your users, and it also has the very useful option of being able to return the full set of rules which apply for a given user. Take a look at the product documentation here to learn more about this feature of AM: Introducing Authorization.

IG has full support for working with the AM authorization engine as the enforcement point. It is capable of intercepting each request to IDM and evaluating it by calling out to AM for policy evaluation. It can also do additional local evaluation prior to passing the request down to IDM. You can see the full details about IG’s policy enforcement feature in the IG documentation: Enforcing Policy Decisions.

Demo

Before jumping into the details about how all of this can be put together, a short demo video may make it easier to understand exactly what it is I am hoping to accomplish with this setup:

Example Configuration

I have put together a project in the forgeops git repository which installs the whole ForgeRock Identity Platform exactly as I show it in the demo above and how I describe below. Feel free to use this project to explore the fine details which I might gloss over when I explain how all of this works. You can also use this project as the starting point for your own delegated administration project – after all, it is much easier than starting from scratch! Be aware that the code and configuration provided in this sample are not supported – they are just examples of how you might go about using the supported products they build upon. Also be aware that this project is oriented towards demonstrating functionality – it is not hardened for production. Use your own product expertise as you normally would when considering production deployment practices.

https://stash.forgerock.org/projects/CLOUD/repos/forgeops/browse/sample-platform?at=refs%2Fheads%2Frelease%2F5.5.0

Architecture

The basic architecture you would need to make the most of each of these products is as follows:

 

AM

AM is configured as the authentication provider and the authorization policy decision point. For it to perform this role, it will need a “Policy Set” defined for IDM; this is the collection of policy rules which apply specifically to requests for IDM REST APIs. Since each request to the IDM REST APIs is essentially just a basic HTTP call, you can use the default “URL” resource type provided by AM. You will need to define a policy for each call you expect your REST client to make; for example, if your REST client makes calls like: GET /openidm/info/login you will need to declare a policy which allows this request. Such a policy would look like this:

{
 "data" : {
   "_id" : "info",
   "name" : "info",
   "active" : true,
   "description" : "",
   "resources" : [ "*://*:*/openidm/info/login" ],
   "actionValues" : {
     "GET" : true
   },
   "applicationName" : "openidm",
   "subject" : {
     "type" : "AuthenticatedUsers"
   }
 }
}

This simple policy just states that any authenticated user is allowed to perform a GET action on the “/openidm/info/login” resource (irrespective of the protocol/host/port). You could use the AM Admin UI to define this policy; it would look something like this if you did:

 

Defining appropriate policies for your IDM needs could vary considerably. Take stock of each IDM REST call you need to make and consider the conditions under which users are allowed to make them. Use this to craft a policy set in AM which aligns with all of those details.

IG

After you have defined your policies in AM, you can start enforcing them with IG. IG needs to be positioned in your network topology so that all HTTP requests made to IDM can be intercepted by the IG reverse proxy, and also so that IG can request policy decisions about those requests from AM. This is a standard deployment model for IG – very little is different about how you would deploy IG to protect IDM compared with how you would use it to protect any other HTTP application.

The main thing you need to configure within IG is the PolicyEnforcementFilter. This is a supported, out-of-the-box filter and can be configured in many ways, all of which are described in the filter documentation. An example of one such configuration:

 {
   "type": "PolicyEnforcementFilter",
   "config": {
     "openamUrl": "${env['OPENAM_INSTANCE']}",
     "cache": {
       "enabled": true,
       "defaultTimeout": "1 hour",
       "maxTimeout": "1 day"
     },
     "pepUsername": "openidm",
     "pepPassword": "openidm",
     "pepRealm": "/",
     "application": "openidm",
     "ssoTokenSubject": "${session.openid.id_token}",
     "environment": {
       "securityContextPath": [
         "${session.idmUserDetails.authorization.component}/${session.idmUserDetails.authorization.id}"
       ],
       "securityContextRoles": "${session.idmUserDetails.authorization.roles}"
     }
   }
 }

This example configuration passes in environment details which are stored in the IG session and relate to the currently-authenticated user, as it is defined in IDM. In particular, it passes in the user’s authorization roles and their IDM-specific REST resource path. Depending on how you decide to define your AM policies, these details may or may not be needed.

Based on the form of delegation that you need for your users, this may be all of the filtering you need to declare in IG. However, if you want to perform more fine-grained access control over subsets of records a user can modify, then you willl need additional filters. Those are described below (under “Scoping Data”).

Assuming the filters allow the request to continue, you will need to be sure that IG provides relevant user details in the request to IDM. The simplest solution is to augment the request by adding a new header value which identifies the user; IDM will read this header from the request and use it as part of its own basic authentication framework.

IDM

Since IG has taken responsibility for validating authentication and authorization, IDM simply needs to be configured to no longer attempt to perform these duties itself. There are two main changes that you need to make to the IDM configuration to make this possible:

conf/authentication.json

This configuration entry describes how IDM performs authentication. Every request to an IDM REST endpoint has to have some form of authentication, even if it is very basic; IDM provides several different options for this. The easist option to use with IG is the TRUSTED_ATTRIBUTE authentication  module; see the authentication module documentation and the associated sample documentation for details on how this works. Essentially, the ‘X-Special-Trusted-User’ header contains the name of the user performing the request. It is set by IG, and it is trusted by IDM to be accurate. This trust is why it is so important that IG be the only entry point into IDM – otherwise, an attacker could supply their own header value and pretend to be anyone. Here’s an example module configuration:

 {
   "name" : "TRUSTED_ATTRIBUTE",
   "properties" : {
     "queryOnResource" : "managed/user",
     "propertyMapping" : {
       "authenticationId" : "_id",
       "userRoles" : "authzRoles"
     },
     "defaultUserRoles" : [ "openidm-authorized" ],
     "authenticationIdAttribute" : "X-ForgeRock-AuthenticationId",
     "augmentSecurityContext" : {
       "type" : "text/javascript",
       "file" : "augmentSecurityContext.js",
       "globals" : {
         "authzHeaderName" : "X-Authorization-Map"
       }
     }
   },
   "enabled" : true
 }

conf/router.json

This entry defines scripted filters which apply to every IDM API request, whether from HTTP or from internal API calls. By default IDM has its own authorization logic specified as the first filter; it is the one which invokes router-authz.js. Since IG and AM are performing authorization, you can simply remove this filter.

This is actually all that is required for you to change in IDM, at least for the REST API level. At this point the REST API in IDM should be protected by the AM policy engine.

Scoping Data

The request from IG to the AM policy engine only includes certain, high-level details about the request. IG asks AM things like “what actions can this user perform on the resource /openidm/managed/user/01234“. Generally, AM policies are pattern-based; this means that AM can only tell if the user can perform actions on a set of resources, such as “/openidm/managed/user/*“. If you need rules governing the actions a given user can perform on a subset of resources within that pattern, AM probably does not and cannot have enough information about the resources to make a decision on its own. However, there is a way to return information to IG about the user which IG can use to make its own decision about the request.

Response Attributes

An AM policy can return data along with the results of the policy evaluation in the form of “response attributes”. These attributes can be static values defined within the policy or they can be dynamic values read from the user’s profile. It is up to IG to decide what meaning to impart upon the presence of these attributes. For example, IG can use these response attributes to decide whether a request for a particular resource falls within the subset of resources available to the current user.

To continue to build upon the “/openidm/managed/user/01234” question above, an AM policy can be declared which indicates that the current user is allowed to perform some actions on the general case of “/openidm/managed/user/*” and also send back a response attribute – for example, the organization they belong to. IG can then include another filter that runs after the PolicyEnforcementFilter which is configured to look for the presence of this response attribute.

Scripted Filter Based on Policy Response

Before the request is forwarded to IDM, IG can take additional action based on the response attributes recieved from the policy decision. For example, IG can query IDM to verify that /openidm/managed/user/01234 is within the same organization as the user making the request. If the user is not found in the query results, then this filter can simply reject the request before it is sent to IDM.

Here is an example scripted filter which performs this function:

 {
   "name": "ScopeValidation",
   "type": "ScriptableFilter",
   "config": {
     "type": "application/x-groovy",
     "file": "scopeValidation.groovy",
     "args": {
       "scopeResourceQueryFilter": "/organizationName eq \"\\${organizationName}\"",
       "scopingAttribute": "organizationName",
       "failureResponse": "${heap['authzFailureResponse']}"
     }
   }
 }

This “scopeValidation.groovy” example looks for the presence of a “scopingAttribute” as a response attribute from the earlier policy decision and uses it to construct a query filter. This query filter is then used to define the bounds within which a particular user can perform actions on subsets of resources. It is also used when the user is querying resources – whatever filter they supplied will be altered to also include this filter too. For example, if they call GET /openidm/managed/user?_queryFilter=userName eq “jfeasel” then the resulting query that is passed down to IDM would actually be this: GET /openidm/managed/user?_queryFilter=((userName eq “jfeasel”) AND (/organizationName eq “example”)). The end result is that the user only sees the subset of resources they are allowed to see.

This is just one example of how you could achieve scoping. The power of scripting in IG means that if this example is insufficient for your needs, you can easily alter the groovy code for this filter to account for any variation you might need.

Discovery

Having each REST call validated as it is made is clearly the most essential behavior required. That being said, another very important aspect of delegated administration is being able to show users only those options which are actually available to them. It is a very poor user experience to show all options followed by “Access Denied” type messages when they try something they were not allowed to do.

Fortunately, the AM policy engine has a way for users to discover which features are available to them. See the documentation for “Requesting Policy Decisions For a Tree of Resources“. Making a request to policies?_action=evaluateTree returns values like so:

[
 {
   "advices": {},
   "ttl": 9223372036854776000,
   "resource": "*://*:*/openidm/info/login",
   "actions": {
     "POST": true,
     "GET": true
   },
   "attributes": {}
 },
 {
   "advices": {},
   "ttl": 9223372036854776000,
   "resource": "*://*:*/openidm/managed/user/*",
   "actions": {
     "PATCH": true,
     "GET": true
   },
   "attributes": {
     "organizationName": [
       "example"
     ]
   }
 }
 .....
]

This is the fundamental building block upon which you can build a dynamic UI for your delegated admins. The next step necessary is a means to return this information to your users. The policy engine endpoints (such as the above call to evaluateTree) are not normally accessible directly by end-users; only privileged accounts can access them. The best method for returning this information to your users is to define a new endpoint in IG which has the same configuration details that the PolicyEnforcementFilter uses. This new endpoint is a ScriptableHandler instance which is basically just a thin wrapper around the call to evaluateTree.

An example implementation of this endpoint is available within the “sample-platform” folder within the forgeops git repository. Here are the two key files:

With this /policyTree/ endpoint available, your UI can make a simple GET request and find every detail about which REST endpoints are available for the current user. You can then write UI code which relies on this information in order to render only those features which the user has access to use. Example code which extends the default IDM Admin UI is also available in the sample-platform folder; take a look at the files under idm/ui/admin/extension for more details.

Request Sequence

Here is a detailed diagram which demonstates how a series of requests by an authenticated user with the delegated administration role are routed through the various systems:

Diagram available via WebSequenceDiagrams

Next steps

Although I only showed a very simple form of delegation with this example, the pattern described should enable many more complex and powerful use-cases. There are many features of the products which, when used together, could make for some very exciting systems.

This is just the beginning – we will be working toward improving product integration and features to make this use case and many others possible. Be sure to let us know about your successes and your struggles in this area, so that we can keep the products growing in the right direction. Thanks!