An Oracle OIC customer wants to pass a username to OIC integration instances when triggering them via a REST call. But the customer does not want to use a password for security reasons. One way to implement this use case is to set a username in a self (the customer) generated and signed JWT token, use that JWT token to obtain an access code from IDCS and finally use the access code to trigger OIC instances.
There are a few steps that need to take place in order to achieve our goal. In my search for a solution, I found a few blogs that were very helpful in providing information on how to accomplish some of these necessary steps. But in terms of creating a JWT token that is acceptable to IDCS, I have yet found any documentation that provides a working set of instructions (Please see the footnote). After some trial-and-error effort, I managed to create my own JWT token that worked with IDCS. I would like to share my findings with other developers in this blog. I will start by briefly describing all the required artifacts and configurations.
P.S.: It has brought to my attention after this blog was published that my colleague Olaf had published an article on the same topic but using a different JAVA library. Here is the link to his blog: Creating a JWT Token for an Assertion Grant Type Flow
The blog Trigger OIC Integration Using OAuth by Greg Mally provides a detailed guide on these steps. It also has good information on using Postman for testing, which we will do for our own testing.
For our use case, we will set the grant types to Client Credentials and JWT Assertion only as in the following image.
From this step, you will record three values: client ID, client secret, and scope value.
Please follow the blog Authentication and User Propagation for API Calls by Olaf Heimburger to complete the following tasks:
1. Create a key pair in a key store, export the public certificate in a file, and record the alias for your public key.
2. Import the public certificate into the confidential application created in the previous step. This is for signature verification.
3. Import the public certificate into the Partner Setting to set up a trust relationship.
At jwt.io, you can find many JWT libraries for different programming languages. I use jjwt (Java JWT: JSON Web Token for Java and Android) in this exercise. For us non-Maven user, we need to manually download the required libraries:
jackson-annotations-2.11.2.jar
The following code snippets show how to set up a JWT token programmatically.
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import java.io.File;
import java.security.Key;
import java.security.KeyStore;
import java.util.Date;
import java.util.UUID;
...
// Read the private key from the key store
KeyStore ks = KeyStore.getInstance(new File(keyStoreFileName), keyStorePassword.toCharArray());
Key key = ks.getKey(alias, keyStorePassword.toCharArray());
JwtBuilder jwtBuilder = Jwts.builder();
// Set up the header first
jwtBuilder.setHeaderParam("alg", "RS256");
jwtBuilder.setHeaderParam("typ", "JWT");
jwtBuilder.setHeaderParam("kid", "key-alias"); // This property must be specified. If missing, you will get "Invalid User Assertion"
jwtBuilder.claim("sub", "username"); // subject = username
jwtBuilder.claim("prn", "username"); // principle = username
// The following combination works
// jwtBuilder.claim("aud", "https://identity.oraclecloud.com/"); // audience = a fixed value to this value for IDCS
// jwtBuilder.claim("iss", "https://identity.oraclecloud.com/"); // Issuer = fixed
// The following combination works
jwtBuilder.claim("aud", "https://identity.oraclecloud.com/"); // audience = a fixed value to this value for IDCS
jwtBuilder.claim("iss", "c54859b3b7054cxxxxea864cb211a21c"); // Issuer = fixed to IDCS app client id
UUID uuid = UUID.randomUUID();
Date date = new Date();
long iatSeconds = date.getTime() / 1000;
long expSeconds = iatSeconds + 1 * 60 * 60; // set the JWT expiration to 1 hour. Can be longer
jwtBuilder.claim("iat", iatSeconds);
jwtBuilder.claim("exp", expSeconds);
jwtBuilder.claim("jti", uuid.toString());
// Sign the token with the private key
String jwt = jwtBuilder.signWith(key).compact();
Here is the JWT token we build in the Java code above in JSON format (header.payload):
{ "alg": "RS256", "typ": "JWT", "kid": "key-alias"} . {"sub": "username", "prn": "username", "aud": "...", "iss": "...", "iat": 1234, "exp": 1256, "jti": "uuid..."}
Follow the images below to set up Postman to use the JWT token to retrieve an access code from IDCS:
If everything works, you should get an access code back.
In case you run into errors, you can get some information from IDCS by turning on Diagnostics as shown in the image below:
Now you can do your test and download the log file like the following:
I found the following error in my downloaded csv file when I did not set the "kid" attribute in the JWT header.