Based in Melbourne, Australia.

Developer
Life

Setting Custom OAuth Tokens in ISAM

Setting Custom OAuth Tokens in ISAM

Contents

Overview

In the early days, to authenticate and authorize users, many developers had to look to proprietary approaches when integrating with external identity providers. Luckily this didn't stick around as SAML (Security Assertion Markup Language), an open XML based standard message type was introduced. This later resulted in OAuth and OAuth 2.0, a more RESTful approach using JSON objects. This then evolved into the pista resistance that is OpenID Connect (OIDC) - which exists on top of OAuth 2.0.

With OIDC, a trusted identity provider is used to prove that you are who you say you are. This is achieved through the exchange of signed JWTs (JSON Web Tokens), which leaves the protection of your identity to the OP (OpenID Provider) and not the RP (Relying Party).

There are three types of tokens that are used in an OIDC flow. They are the:

  • id_token Contains information about an authentication event and claims about the authenticated user.
  • access_token Used to call protected API's, e.g. /userinfo
  • refresh_token Used to generate additional access tokens.

ISAM automatically creates these for us, which means we don't have to worry to much about them, but what happens if we want to customize the values of these tokens?

Well...

API Definition Mapping Rules

In ISAM, the OAuth flow passes over the API Definition Mapping Rules. For every OAuth API definition, there are two mapping rules that automatically get created:

  • Pre-Token Mapping Rule
    This mapping rule in ISAM is triggered before token or attribute validation. This means we can validate credentials, such as username or password, and use it for a variety of other mechanisms.
  • Post-Token Mapping Rule
    This mapping rule is triggered after the tokens have been generated or exchanged with the RP. Here we can access user attributes stored against the OAuth token.

Setting A Custom ID Token

In our OAUTH PreTokenGeneration.js we can add the following section of code to create a custom id_token which will be returned to the RP on the /oauth20/authorize call.

OAuth PreTokenGeneration mapping-rule
importPackage(Packages.com.tivoli.am.fim.trustserver.sts);
importPackage(Packages.com.tivoli.am.fim.trustserver.sts.oauth20);
importPackage(Packages.com.tivoli.am.fim.trustserver.sts.uuser);
importPackage(Packages.com.ibm.security.access.user);
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
importClass(Packages.java.util.ArrayList);
importClass(Packages.java.util.HashMap);
importClass(Packages.java.lang.System);

const SESSION_LIFETIME = 3600; // 60 minutes.
const NOT_BEFORE = 60; // 1 minute.

if (request_type == "access_token") {

user = getAttributeValue("oidc_username", "urn:ibm:names:ITFIM:5.1:accessmanager", ATTRIBUTE, "");

now = new Date();
claims = {};
claims.auth_time = Math.floor(now.getTime() / 1000);
claims.exp = SESSION_LIFETIME; // Optional: Will override value set in API Definition.
claims.nbf = Math.floor(now.getTime() / 1000 - NOT_BEFORE);
claims.sub = "" + user.toUpperCase();
claims.jti = "" + java.util.UUID.randomUUID();

stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("claim_json", "urn:ibm:oidc10:jwt:create", JSON.stringify(claims)));
// Overwrite `sub` with the `user_id`.
stsuu.addAttribute(com.tivoli.am.fim.trustserver.sts.uuser.Attribute("sub", "urn:ibm:jwt:claim", "" + claims.sub));
}

If we need to add a custom X.509 certificate thumbprint to the token header, you can do this by adding the following line.

OAuth PreTokenGeneration mapping-rule
const X5T_KEY = "X2maFm3VYlFcJHY71G7nTaYiZaOm";
// Add X.509 Fingerprint (SHA-1) to token.
stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("x5t", "urn:ibm:JWT:header:claim", X5T_KEY));

Setting A Custom Access Token

To return a custom access_token to the RP, similar to how we we set a custom id_token, we want to add the below into our OAUTH PreTokenGeneration.js mapping rule.

OAuth PreTokenGeneration mapping-rule
if (request_type == "access_token") {
accessToken = "" + java.util.UUID.randomUUID();

stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("urn:ibm:ITFIM:oauth20:custom:token:access_token", "urn:ibm:ITFIM:oauth20:custom:token", accessToken));
stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("urn:ibm:ITFIM:oauth20:custom:token:access_token", "urn:ibm:ITFIM:oauth20:custom:token:persistent", "true"));
}

But what if we want to return a JWT as the access token instead?

To do this, you have to first configure a STS chain. Once done, we use this chain to issue our token.
This means, instead of returning an opaque token, we now return a JWT which we can later be used to validate API calls.

OAuth PreTokenGeneration mapping-rule
importClass(Packages.com.tivoli.am.fim.fedmgr2.trust.util.LocalSTSClient);

const ACCESS_TOKEN_LIFETIME = 1800; // 30 minutes.
const NOT_BEFORE = 60; // 1 minute.

if (request_type == "access_token") {
user = getAttributeValue("oidc_username", "urn:ibm:names:ITFIM:5.1:accessmanager", ATTRIBUTE, "");

let RS256 = {}
RS256.keystore = "jwtKeyStore"; // SSL Certificate Database.
RS256.key = "jwtKey"; // Signing Certificate.

now = new Date();
claims = {};
claims.iat = Math.floor(now.getTime() / 1000);
claims.exp = Math.floor((now.getTime() + (1 * ACCESS_TOKEN_LIFETIME * 1000)) / 1000);
claims.nbf = Math.floor(now.getTime() / 1000 - NOT_BEFORE);
claims.sub = "" + user.toUpperCase();
claims.jti = "" + java.util.UUID.randomUUID();

accessToken = token.issue(claims, RS256);

stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("urn:ibm:ITFIM:oauth20:custom:token:access_token", "urn:ibm:ITFIM:oauth20:custom:token", accessToken));
stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("urn:ibm:ITFIM:oauth20:custom:token:access_token", "urn:ibm:ITFIM:oauth20:custom:token:persistent", "true"));
}

token = {};
token.issue = function (claims, RS256) {
tokenStsuu = new STSUniversalUser();
// Define signing certificate for JWT.
tokenStsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("signing.db", "", "" + RS256.keystore));
tokenStsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("signing.cert", "", "" + RS256.key));
tokenStsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("signing.alg", "", "RS256"));
// Add claims to STSUU.
tokenStsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("claim_json", "", "" + JSON.stringify(claims)));
// Issue JWT token.
stsToken = token.callSts(tokenStsuu.toXML().getDocumentElement(), "urn:jwt:issue");

if (stsToken.token) {
return stsToken.token.getTextContent();
} else {
IDMappingExtUtils.traceString("Error getting token from STS: " + stsToken.errorMessage);
}
}

token.callSts = function (baseToken, identifier) {
requestType = "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue";
tokenResult = LocalSTSClient.doRequest(requestType, identifier, identifier, baseToken, null);

return tokenResult;
}

Here are some useful links you can checkout for additional information.

Setting SameSite and Other Cookie Attributes using WebSEAL

Setting SameSite and Other Cookie Attributes using WebSEAL

Adding Dynatrace to Verify Access - Why You Need This

Adding Dynatrace to Verify Access - Why You Need This