Using OpenID Connect to delegate authentication to Oracle Identity Cloud Service

In this post, I will describe the process of using the Oracle Identity Cloud Service to provide authentication for a custom web application, using the OpenID Connect protocol. I will focus on the sequence of calls between the application and IDCS in order to focus on building an understanding of how OpenID Connect actually works.

The problem we are trying to solve

Before diving into any specifics, let’s take a minute to talk about OpenID Connect and understand why we might want to use it at all. Have a read through the OpenID Connect 1.0 specification before continuing. In a nutshell, OpenID Connect (OIDC) is a “simple identity layer on top of the OAuth 2.0 protocol”. While OAuth itself is often (mis)used to allow for the externalisation or delegation of authentication, it is, by design, a standard that is wholly concerned with authorisation. While it’s generally true that you need to be authenticated before authorisation makes sense, there was never any formalised way to do this within OAuth itself. OIDC is the layer that adds standardised support for authentication and identity in a way that is fully compatible with and completely built on the OAuth 2.0 standard.

We’re going to look at a very simple example of using OIDC to provide authentication for a custom web app. We’ll be using the Authorisation Code flow here, generally a more secure flow because the user agent (i.e. the web browser) never has direct access to any of the tokens involved. The primary reasons for incorporating this functionality into an application are twofold; firstly, we may want to reduce complexity for the application developers, by removing the need to worry about authentication, password storage, user registration and the like. They can simply use an existing cloud service to handle that part for them. Secondly, and perhaps more immediately valuable, is that by doing this, we can participate in single sign-on with other applications that are also integrated with Oracle Identity Cloud Service.

Now, at this point, I do need to point out that OIDC is not Web Access Management; it does not play the same role as a WAM product like Oracle Access Manager or CA SiteMinder. There is no agent here, doing perimeter authentication and managing user sessions on behalf of your app. Your app needs to explicitly invoke an OIDC flow and explicitly handle session lifecycle on its own, dependant on the identity information that it receives back from the OpenID Connect Provider. In fact, you should probably make a point to read Chris Johnson’s excellent post on why SAML is not the same as WAM, because in a lot of ways, OIDC is very similar to SAML in terms of the problems it attempts to solve. OIDC, though, is lightweight and REST/JSON-based, rather than the heavier XML-based SAML protocol.

Overview of the process

Here’s a simple list of steps explaining what our app needs to do (at run time) in order to establish a session and obtain user profile information, using the OIDC Authorization Code flow. I need to point out that you are very unlikely to ever have to implement these steps “from scratch”, since there are many proven, tested OpenID Connect client libraries available for virtually any development platform or language. Treat the below as instructional, but really, don’t try to roll your own in the real world.

1. When the app needs the authenticate the user, it generates a link to the OAuth2 Authorisation endpoint on the OIDC Provider (which is Oracle IDCS). This link includes the “openid” scope, the “code” response type and a local callback URL to which the Provider will redirect the browser once the authentication has been successful.

2. The user clicks the link, which results in an authentication challenge from Oracle IDCS. The user enters their credentials at the IDCS login screen and these are validated. If they are correct, an IDCS session is created for the user (represented by a browser cookie). Note that if the user already has an IDCS session due to a prior authentication, they will not be re-challenged, but will move on to the next step.

3. IDCS generates an authorisation code. This is a short, opaque string that can safely be passed as part of an HTTP payload, since it is not valuable without the corresponding Application credentials. IDCS redirects the user back to the callback URL specified in step 1, appending the code to the URL string.

4. The app extracts the authorisation code from the HTTP payload. It then makes a REST call to the OAuth2 Token endpoint on IDCS. This call is authenticated by passing the app’s client ID and secret in a Basic Auth header. The body of the call includes the authorisation code.

5. Oracle IDCS authenticates the app using the client ID and secret and validates that authorisation code is valid and was issued for that app. It then returns a JSON payload containing both an Identity Token and an Access Token. Both of these tokens conform to the JSON Web Token (JWT) standard.

6. The app uses the public IDCS signing certificate to validate the Identity Token (which contains a signature). This token, once decoded, contains a number of claims that tell the app about the authentication event that took place. These include the subject, the time of authentication, the session expiry time, level of authentication and so on.

7. The app makes a REST call to the UserInfo endpoint on Oracle IDCS. This call is authenticated by passing the Access Token obtained in step 5 as a Bearer Auth header.

8. IDCS validates the provided Access Token, which is specific to the user that authenticated in step 2. The Access Token will include a number of scopes, and based on these scopes, IDCS will return the appropriate user profile information back to the app in a JSON response.

9. Now it’s up to the app! It has all the identity and user profile information it needs in order to create a local user account (if this is a first-time login) and establish a local session for the user.

Required Setup

The first thing you need is to register your app as an OAuth Client with IDCS. This will allow you to obtain the Client ID and Secret you need. Make sure to select “Web Application” as the type.

Register OAuth

Ensure that you select the “Authorization Code” grant type and specify a valid callback URL pointing to an endpoint on your application that can receive and process the code.

Configure OAuth

Finally, take note of your Client ID and Secret. Your app will need to store these securely in an internal credential store and use them when making calls to IDCS.

Client Credentials

The other bit of setup that’s required is for you to obtain the signing certificate that IDCS uses to sign JSON Web Tokens. Your code or JWT library will need to use this certificate to validate the signature of the Identity and Access Tokens that IDCS generates. You can obtain this certificate by issuing a GET request against the following endpoint: <IDCS_HOST>/admin/v1/SigningCert/jwk. You will need to pass an appropriately-scoped Bearer JWT in the Authorization header in order to obtain the certificate.

Authenticating the User

Now that we have the setup completed, we can look at the details. There are three main steps in the OIDC login flow and the first is to send the user to the OIDC Provider for authentication. This is done by redirecting the client browser to a particular URL on the IDCS server and passing parameters, as defined below. Note that this redirect can be accomplished in a number of different ways: the user can explicitly click a link on the app homepage to start the login flow, or the app could perhaps use an intercepting session filter to generate an automatic redirect when it detects that the browser does not have a valid application session.

In either case, the browser should be redirected to the following IDCS URL:
https://<IDCS_HOST>/oauth2/v1/authorize

The following table explain the URL parameters that must be sent when constructing the authentication link:

client_id The Client ID for this application. IDCS uses this ID to tie the eventual authorisation code to the application that initiated the OIDC flow. No other application will be able to use the code. Note that the Client Secret is never passed in a URL string.
response_type This must be specified as “code” since we’re using the authorisation code flow.
redirect_uri This is the URL-encoded address to which the client must be sent back once the authorisation code has been issued. Note that this must exactly match the “Redirect URL” you specified when you registered your application with IDCS. Once again, this is a safety mechanism to ensure that the code is only sent to your application endpoint, and not elsewhere.
scope This must be a space-delimited strong of the required scopes. The only mandatory value here is the “openid” scope, which is required in order for IDCS to generate the ID token. You can add other scopes as well, such as “profile”, “email”, “phone” and “groups”, depending on how much information about the user you need to retrieve later on. This point will become a bit clearer later, when we call the user info endpoint.
state This is an optional parameter that you can use to maintain state within your application once the authentication redirect has taken place and protect against certain attacks.The OIDC spec defines this as an “Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie.” Whatever value your application passes in here will be returned by IDCS along with the authorisation code.
nonce Another optional parameter that you can use to protect against replay attacks. You should generate a strong random string value and associate it with the user session inside your application before passing it to IDCS. IDCS will include the nonce value inside the Identity Token, allowing your application to perform the necessary validation.

Taking the above into account, we redirect the user to the following URL:

https://tenant1.idcs.internal.oracle.com:8943/oauth2/v1/authorize?client_id=0cbf3bc1a3524d47af286f166bb03ef6&response_type=code
&redirect_uri=https%3A%2F%2Fmyapp.oracle.com%2FauthCode
&scope=openid%20profile%20email&state=102345&nonce=AHFG45asd450

The user will need to authenticate:

IDCS Login

And is then redirected back to the following URL:

https://myapp.oracle.com/authCode?
code=AQIDBAUnE761LZohkZk7YCeeUHsC8m_cTK1Ck5VIkyshi7NXcAVX9thOULxfzcAzNMWLptvzo0hpVdqna4kDiZbNbdEXMTEgRU5DUllQVElPTl9LRVkxNCB7djF9NCA=
&state=102345

This completes the first step in the process and we can now move on.

Exchanging the Code for Tokens

Now that the user has been sent back to the application with an authorisation code, we need to execute the second leg of the flow and perform a call back to the OIDC Provider to validate that code and receive the tokens we need. First of all, though, we should examine the “state” parameter value that has been sent back in the redirect to ensure that it matches the one we generated when redirecting the user. Once we’ve done that, our app needs to extract the “code” parameter value from the URL strong and pass this to IDCS via a back-channel REST call. I say “back channel” because this is a direct call from the application to IDCS that does not involve the user’s browser in the flow. This point is key, because it ensures security of the application’s client secret and also adds a layer of “hijack prevention”, ensuring that any party that may intercept the browser redirect and obtain the authorisation code is not able to use it.

There are a few things we need to do here. First, we use the application’s Client ID and Secret to form a basic authorisation header. We do this by concatenating them together with a colon delimiter, then Base64 encoding the whole thing. Thus 0cbf3bc1a3524d47af286f166bb03ef6:0359486e-d5ac-4339-96ca-96686a9cc223 becomes MGNiZjNiYzFhMzUyNGQ0N2FmMjg2ZjE2NmJiMDNlZjY6MDM1OTQ4NmUtZDVhYy00MzM5LTk2Y2EtOTY2ODZhOWNjMjIz and it’s this value that we pass as our basic authorisation header. We POST to the IDCS token endpoint, which is at https://<IDCS_HOST>/oauth2/v1/token, passing the following values in the request body:

grant_type This is set to “authorization_code”
code The value of the authorisation code we received.

Here’s an example, using CURL:

curl -k –header “Authorization: Basic MGNiZjNiYzFhMzUyNGQ0N2FmMjg2ZjE2NmJiMDNlZjY6MDM1OTQ4NmUtZDVhYy00MzM5LTk2Y2EtOTY2ODZhOWNjMjIz” -d “grant_type=authorization_code&code=AQIDBAUnE761LZohkZk7YCeeUHsC8m_cTK1Ck5VIkyshi7NXcAVX9thOULxfzcAzNMWLptvzo0hpVdqna4kDiZbNbdEXMTEgRU5DUllQVElPTl9LRVkxNCB7djF9NCA=” https://tenant1.idcs.internal.oracle.com:8943/oauth2/v1/token

What we get back, assuming our code is valid and has not yet expired, is a JSON response similar to the following. Note that I’ve shortened the token values to make the output more readable:

{“access_token”:”eyJ4NXQjUzI1NiI6Ijg1a3E1….3Sf4u9k”,
“token_type”:”Bearer”,
“expires_in”:3600,
“id_token”:”eyJ4NXQjUzI1NiI6Ijg1a3E1MFVBV…a4B2iVpjbohdSY”}

Note that we receive two tokens back – both of them are standard JSON Web Tokens (JWT’s). The ID Token is the one that we need to use first, since it’s this token that tells us about the authentication event that has taken place. We should use a JWT library within our application to validate the signature of the token we receive, using the certificate obtained earlier and then we should inspect the body of the token. I’ve used an online tool to parse the ID Token and this is what it contains in the payload:

{
“user_tz”: “America/Chicago”,
“sub”: “rob.otto”,
“user_locale”: “en”,
“user_displayname”: “Rob Otto”,
“csr”: “false”,
“sub_mappingattr”: “userName”,
“iss”: “https://identity.oraclecloud.com/”,
“tok_type”: “IT”,
“user_tenantname”: “tenant1”,
“nonce”: “AHFG45asd450”,
“sid”: “f61d5f79-1dad-40b1-ae7c-f04e9453ad87”,
“aud”: [
“https://identity.oraclecloud.com/”,
“0cbf3bc1a3524d47af286f166bb03ef6”
],
“user_id”: “9f12e029a3434918ad8d096ebc6f96ba”,
“authn_strength”: “2”,
“auth_time”: “1478172094”,
“session_exp”: 1478200894,
“user_lang”: “en”,
“exp”: 1478200894,
“iat”: 1478179463,
“tenant”: “tenant1”,
“jti”: “0aeb7369-33d1-4050-bae2-1e57596c9b2c”
}

Now, before we do anything at all with this token, we must check the “nonce” value and ensure that it matches the random value that we passed through to IDCS in the first step. That tells us that this is the correct ID token for the user that performed the authentication and can help mitigate any replay-type attacks.

Other than that, we have all the basic information we need in order to create a session for this user within our application. IDCS has returned the authenticated subject “rob.otto”, the user’s display name “Rob Otto”, the opaque/non-transient user ID and also time stamps indicating when the user was authenticated and when their login session will expire. Depending on our needs, we may stop here, or, if needed, we can carry on with the third part of the flow to obtain further user profile information.

Obtaining the User Profile

If our application requires more information and if we included additional scopes such as “profile” or “email” in our initial authentication request, we can make a further call back to IDCS to obtain this information. The key here is the other token we received from the token end point, the Access Token. Again, we can inspect this token to see that the body looks as follows:

{
“user_tz”: “America/Chicago”,
“sub”: “rob.otto”,
“user_locale”: “en”,
“user_displayname”: “Rob Otto”,
“user.tenant.name”: “tenant1”,
“csr”: “false”,
“sub_mappingattr”: “userName”,
“iss”: “https://identity.oraclecloud.com/”,
“tok_type”: “AT”,
“user_tenantname”: “tenant1”,
“client_id”: “0cbf3bc1a3524d47af286f166bb03ef6”,
“sid”: “f61d5f79-1dad-40b1-ae7c-f04e9453ad87”,
“aud”: “https://tenant1.idcs.internal.oracle.com:8943”,
“user_id”: “9f12e029a3434918ad8d096ebc6f96ba”,
“scope”: “openid profile email”,
“client_tenantname”: “tenant1”,
“user_lang”: “en”,
“exp”: 1478183063,
“iat”: 1478179463,
“client_name”: “MyOIDCClient”,
“tenant”: “tenant1”,
“jti”: “cc49cfad-079c-408b-9aff-354e032b2a3e”
}

This is a standard scoped OAuth JWT that allows us to call back to IDCS on behalf of the logged-in user. As we can see, our token includes the “profile” and “email” scopes, which will allow us to obtain some further information about our user that can be useful in building a local profile.

Our application can obtain the information it needs by making a simple GET request to the UserInfo endpoint on IDCS, which is here: https://<IDCS_HOST>/oauth2/v1/userinfo. There is no need to do anything special, other than passing the access token we obtained as a Bearer Authorisation header. Here’s an example of the CURL command – again, I’ve shorted the access token to keep things all on one line:

curl -k –header “Authorization: Bearer eyJ4NXQjUzI1NiI6Ijg1a3E1….3Sf4u9k” https://tenant1.idcs.internal.oracle.com:8943/oauth2/v1/userinfo

The JSON object we receive back is self-explanatory and contains the necessary information about our user:

{“birthdate”:””,
“email”:”robert.otto@oracle.com”,
“email_verified”:true,
“family_name”:”Otto”,
“gender”:””,
“given_name”:”Rob”,
“name”:”Rob Otto”,
“preferred_username”:
“rob.otto”,”sub”:
“rob.otto”,
“website”:””}

We can use this information within our application to build a local user profile and van even accomplish “just in time” provisioning of the user from IDCS if this fits our need.

In Conclusion

This post has demonstrated, in detail, one of the simpler OpenID Connect authentication flows and has built on it further to show how user registration can be accommodated as well. There is a huge amount more than can be done using Oracle Identity Cloud Service and it’s support for OAuth 2.0 and OpenID Connect. Let me know via the comments if you have any other use cases in mind that I can dive into further.

Thanks for reading and have fun diving into Oracle Identity Cloud Service.

Comments

  1. Ricardo Rudolph says:

    Rob, thanks for sharing this article; very interesting and useful for me. Just a couple of questions :

    1.- Is OIDC supported in previous versions of Oracle Identity Manager (not Cloud ones) ?
    2.- Is OIDC 2.0 currently supported in any of the OIM/OICS solutions ?

    Thanks !

  2. Angelo Carugati says:

    Great post, Rob!

    I have long been looking for such a clear, concise and nevertheless complete description of OpenID Connect: what exactly it adds to OAuth 2.0 and, even more interesting because it is not documented elsewhere (as far as I know), the details of its mechanisms.

    Angelo Carugati.

Add Your Comment