Unlocking Oracle Analytics Cloud with OAuth 2.0

Mike Durran
Oracle Developers
Published in
9 min readSep 12, 2022

--

Over the last few years we’ve seen the increasing use of OAuth 2.0 access tokens with Oracle Analytics Cloud (OAC). From my own experience, there is a learning curve associated with obtaining and using access tokens and I hope this blog will be useful to anyone starting to use this aspect of OAC. This isn’t intended to be a definitive guide to OAuth 2.0, but rather a way for OAC users to ‘kick-start’ their understanding and usage of access tokens.

There are plenty of resources available online that provide introductions to OAuth 2.0. The site https://oauth.net/2/ is a good place to start. You will also find details of the associated RFC references on that site. This RFC is a good introduction to the OAuth 2.0 Authorization Framework and the problems it was created to solve.

Terminology

Let’s start with a description of some terms used in this blog:

Access Token
An access token is used to gain access to Oracle Analytics Cloud. This could be an embedding use case, or to use OAC APIs, or some other reason. The OAuth 2.0 RFC describes an access token as a ‘string denoting a specific scope, lifetime and other access attributes.’ Access tokens can take various formats. I like this description of access tokens using the analogy of hotel room key cards.

Bearer Token
A bearer token is a type of access token. The token types, used with Oracle Analytics Cloud are bearer tokens. It’s described as an opaque string, not intended to have any meaning to clients using it. The tokens used with Oracle Analytics are structured tokens, in the form of JSON Web Tokens or JWTs.

JSON Web Token (JWT)
To quote Wikipedia, ‘a JWT (pronounced ‘jot’) is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims. The tokens are signed either using a private secret or a public/private key.’ This will become more relevant when we discuss JWT assertions later in this blog. I’ve also included some references below that go into more detail on JWTs. This site is a useful resource for ‘decoding’ JWTs if needed for troubleshooting.

Refresh Token
When you obtain an access token, you can optionally choose to also obtain a refresh token. A refresh token can be used to obtain an access token using a different API payload than is used to obtain the original access token. As stated above, an access token has a specific (configurable) lifetime associated with it and a refresh token provides a mechanism to obtain a new access token. Refresh tokens will typically have a longer lifetime than access tokens. To obtain a refresh token, add ‘offline_access’ to the scope parameter when making calls to the IDCS /oauth2/v1/token endpoint.

Token Expiry
When you obtain an access token, it will expire after a configurable amount of time. For Oracle Analytics, this is 100s by default. This can be changed by editing the IDCS application for the OAC instance (not the confidential application that is created in the next section of this blog). Note that refresh tokens typically have a much longer expiry time. If you wish to change the token expiry time using an API, that process is described here.

Confidential Application
This term refers to an application that can keep credentials such as a ‘client secret’ secure. For Oracle Analytics Cloud, a confidential application is created in Oracle Identity Cloud Service or Oracle IAM.

Client ID
This is an identifier that is issued to the confidential application.

Client Secret
This is the corresponding ‘secret’ associated with the confidential application. This can be renewed if needed.

Scope
As we will see later in this blog, when you make a call to an API to obtain an access token for use with Oracle Analytics Cloud, you need to provide a scope as part of the payload. The scope provides a way to define the resources that the access token allows to be used. In our OAC use cases, this is usually the OAC instance mapped in the IDCS confidential application.

Creating a Confidential Application

You will need the associated privilege to create applications within Oracle IDCS / IAM. The first step is to choose to ‘Add’ a new Confidential Application:

Provide a name for the Confidential Application:

Click ‘Next’ and select ‘Configure this application as a client now’:

This screen is where you choose the grant types (more on those later) in addition to where you upload a public key and define an alias if you are using JWT signed assertions.

If you scroll down, you will see the option to ‘Add Scope’ in the resources section. This is where you map the Oracle Analytics Cloud instances you want to generate access tokens for.

Click ‘Next’ through the remaining screens, until you can click ‘Finish’. At this point you will be prompted that the application has been added and presented with the Client ID and Client Secret for that application. Don’t forget to ‘Activate’ the application.

The Easiest Way to Obtain an Oracle Analytics Cloud Access Token

In the Oracle IDCS console, if you navigate to the ‘Oracle Cloud Services’ option on the left, and then select the OAC instance for which you want to obtain a token. You will notice a button ‘Generate Access Token’ that, when selected, will allow you to choose whether to also include a refresh token and then enable you download an access token.

This could be all you ever need! If you ‘bootstrap’ a process with an initial access token obtained in this manner, and then obtain subsequent access tokens using a refresh token. This is an example of the curl command to obtain a new access token using a refresh token:

curl --location --request POST 'https://<IDCS INSTANCE>.identity.oraclecloud.com/oauth2/v1/token' \
--header 'Authorization: Basic <base64 encoded clientID and clientSecret>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=<REFRESH TOKEN OBTAINED FROM SECOND CURL COMMAND>' \
--data-urlencode 'scope=<SCOPE FROM IDCS CONFIDENTIAL APP>'

If your use case requires obtaining access tokens programmatically, then you’ll need to use one of the methods described below which allows you to get an access token (as well as refresh tokens) using an API.

Grant Types

In this section, I’ll describe examples of grant types that can be used to obtain an access token for use with Oracle Analytics Cloud.

Resource Owner Password Credentials

This grant type requires the inclusion of the username and password in the payload of the API call so it may not be suitable for all use cases. In fact, this grant type is not recommended for use by the OAuth 2.0 best practices guide.

Also, this grant type doesn’t work if you are using federated users with your Oracle Analytics Cloud instance. It will only work if your user is defined in Oracle IDCS itself.

This is an example of a curl command to obtain an access token using this grant type. The parameters that need to be replaced according to your own values are e.g., <PARAMETER>. Note that if ‘offline_access’ is added to the end of the scope parameter then a refresh token will also be returned:

curl --request POST '<IDCS INSTANCE>/oauth2/v1/token' \
--header 'Authorization: Basic <BASE64 ENCODED client ID:Client Secret> ' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=<USERNAME>' \
--data-urlencode 'password=<PASSWORD>' \
--data-urlencode 'scope=<SCOPE FROM IDCS CONFIDENTIAL APP> offline_access'

Device Code

As my colleague has described in this blog, the device code grant is a good option for obtaining an access token to use with Oracle Analytics Cloud. It works with federated users and provides a mechanism for obtaining an access token by a user authenticating to Oracle IDCS then subsequently using a refresh token to obtain new access tokens on expiry of existing tokens. I’ll illustrate use of this grant type using three curl commands.

In the first curl command, we call the IDCS API to obtain a device code and user code, substituting parameters <PARAMETER> accordingly :

curl --location --request POST 'https://<IDCS INSTANCE>.identity.oraclecloud.com/oauth2/v1/device' \
--header 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
--data-urlencode 'response_type=device_code' \
--data-urlencode 'scope=<SCOPE FROM IDCS CONFIDENTIAL APP> offline_access' \
--data-urlencode 'client_id=<CLIENT ID FROM IDCS CONFIDENTIAL APP>'

The response from this curl command will be similar to this. Note the verification URI that you must login as your user ID and pass the user_code :

{"device_code":"12a42e616264123ccb913a86df39c1397","user_code":"QYWNNPSS","verification_uri":"https://<IDCS INSTANCE .identity.oraclecloud.com/ui/v1/device","expires_in":300}

Once you’ve passed the user_code to the UI that appears when logged into the provided IDCS URI, you will then run this second curl command, substituting <PARAMETERS> accordingly:

curl --location --request POST 'https://<IDCS INSTANCE>.identity.oraclecloud.com/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
--data-urlencode 'client_id=<CLIENT ID FROM IDCS CONFIDENTIAL APP>' \
--data-urlencode 'client_secret=<CLIENT SECRET FROM IDCS CONFIDENTIAL APP>' \
--data-urlencode 'device_code=<DEVICE CODE RETURNED FROM FIRST CURL COMMAND>'

IDCS is waiting for you to execute this second curl command (note the expiry time) and will return an access token (and optionally a refresh token if ‘offline_access’ is used in the scope parameter).

If you want to use the refresh token, on expiry of the access token to obtain a new access token, you can use this curl command:

curl --location --request POST 'https://<IDCS INSTANCE>.identity.oraclecloud.com/oauth2/v1/token' \
--header 'Authorization: Basic <base64 encoded clientID and clientSecret>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=<REFRESH TOKEN OBTAINED FROM SECOND CURL COMMAND>' \
--data-urlencode 'scope=<SCOPE FROM IDCS CONFIDENTIAL APP>'

JWT Assertion

This grant type can also be used with federated users and requires some additional steps to create the signed JWT assertions for both the client and user. This blog provides an excellent overview of the overall process and I recommend reviewing this blog before using the script below.

When you create the IDCS confidential app, make sure to choose the ‘Client Credentials’ as well as ‘JWT Assertion’ grant types. Also, select ‘Trusted’ for the ‘Client Type’ option.

The following Python script, will generate the signed assertions for both the client and user. It will then make a call to the Oracle IDCS API to obtain the access and refresh tokens. The Python script has dependencies on the PyJWT and ‘requests’ packages that can be installed using pip.

from datetime import datetime, timezone
import jwt
import requests
tokenIssued = int(datetime.now(tz=timezone.utc).timestamp())
tokenExpiry = tokenIssued + 3600
# These are the parameters you need to substitute for the values for
# your OAC instance and IDCS instance and confidential app
# The scope also includes 'offline_access' in order to also return a # refresh token
clientId = '<CLIENT ID FROM IDCS CONFIDENTIAL APP>'
certAlias = '<CERTIFICATE ALIAS FROM IDCS CONFIDENTIAL APP>'
userToAssert = '<USER TO ASSERT>'
OACscope = '<OAC SCOPE FROM IDCS CONFIDENTIAL APP> offline_access'
IDCS_url = 'https://<IDCS INSTANCE>.identity.oraclecloud.com/oauth2/v1/token'
private_key = open('<path to private RSA key>', 'r').read()# The following details are used to create the JWTheader = {
"alg": "RS256",
"typ": "JWT",
"kid":certAlias
}
client_payload = {
"sub":clientId,
"iss":clientId,
"aud": ["https://identity.oraclecloud.com/"],
"iat":tokenIssued,
"exp":tokenExpiry
}
user_payload = {
"sub":userToAssert,
"iss":clientId,
"aud": ["https://identity.oraclecloud.com/"],
"iat":tokenIssued,
"exp":tokenExpiry
}
# Create the signed assertionsencoded_user_assertion = jwt.encode(
payload = user_payload,
headers=header,
key=private_key,
algorithm="RS256")
encoded_client_assertion = jwt.encode(
payload = client_payload,
headers=header,
key=private_key,
algorithm="RS256")
# Create the payload and headers for the call to the IDCS API to
# obtain the access token
payload = {
'grant_type' : 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'scope' : OACscope,
'client_id' : clientId,
'client_assertion_type' : 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'assertion' : encoded_user_assertion,
'client_assertion' : encoded_client_assertion
}
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
# Call the IDCS API and output the access tokenresponse = requests.request("POST", IDCS_url, headers=headers, data=payload)print(response.text)

References

The following references have been useful in the creation of this blog:
OAuth 2.0
How to handle JWTs in Python.
Red Thunder Blog describing JWTs and related items.
Red Thunder Blog on using public / private key authentication for Oracle IDCS.
Description of confidential and public applications from Auth0 website.

Acknowledgements

Amit Chakraborty for his guidance on the device code grant type.
Gabrielle Prichard for guidance on python scripting and reviewing drafts of this blog.

Want to discuss this or other developer topics? Join our public Slack channel!

--

--

Mike Durran
Oracle Developers

Analytics Product Manager at Oracle. [All content and opinions are my own]