IBM Support

Fine-Tuning ID Tokens in ISAM Advanced Access Control for OIDC Flows

Question & Answer


Question

I've successfully configured an API Protection Definition for Open ID Connect Federation.
I've been able to successfully acquire an 'id_token' but it only has the 'subject' and no claims data.
How do I accomplish this in when using ISAM Advanced Access Control?

Cause

The Open ID Connect 'ID Token' contents are controlled by the contents of the STSUniversal User document during the request to the '/sps/oauth/oauth20/token' endpoint.
We need to update our mapping rules to add desired claims.

Answer

 

To put additional claims into the resultant JWT that is acquired during an Authorization Code or Implicit flow you must utilize the PreTokenGeneration and PostTokenGeneration mapping rules associated with your API Protection Definition.

The mapping rules in question will have a naming convention of :
<api_protection_definition_name>PreTokenGeneration
<api_protection_definition_name>PostTokenGeneration

Example :
image-20190328184842-1

There are three ways one can get to the mapping rules :

A) Secure Access Control -> Global Settings -> Mapping Rules
B) Secure Access Control -> Policy -> OpenID Connect and API Protection ->> Mapping Rules
C) Secure Federation -> Global Settings -> Mapping Rules

Option 'B' only shows mapping rules of category 'OAUTH' while the other options show all the mapping rules.

Next steps are to develop the mapping rules to perform an action depending on your OpenID Connect Scenario.

i) Preparing your development environment

Best practice is to either download or copy the contents of the mapping rule from the appliance to another development IDE. Notepad++ is a good text editor that supplies basic text editing functionality and has the ability to use many extra modules.

One should also have the Java Documentation open for reference. The Java Documentation can be downloaded at the following locations on each appliance :
Manage System Settings -> Secure Settings -> File Downloads ->> access_control -> doc -> ISAM-javadoc.zip
Manage System Settings -> Secure Settings -> File Downloads ->> federation -> doc -> ISAM-javadoc.zip

Scenario 1) OpenID Connect Federation using the Authorization Code flow

In this scenario, the end user will access the '/mga/sps/oauth/oauth20/authorize' endpoint with Parameters relative to their OpenID Connect context for the 'Authorization Code' OAUTH flow.

The user will be required to authenticate to the Reverse Proxy and this authenticated session will have information about the user such as email addresses, usernames, authentication methods, and other attributes that can be acquired via the Reverse Proxy or Attribute Sources.

After the user authenticates they may be prompted to authorize various access scopes and give consent to the app to access their data.

After successful consent they will receive an authorization code that can be exchanged for an 'Access Token' and an 'ID Token' at the '/mga/sps/oauth/oauth20/token' endpoint.

 

1) Authorization Phase : Storing Attributes for later use -- PreTokenGeneration


During a request to the '/authorize' endpoint the user will be required to authenticate.
This gives the Advanced Access Control component access to attributes in the user's credential.

ISAM Reverse Proxy instances can be configured to retrieve attributes from LDAP as part of the authentication process. Reference to this will be included at the end of the document.

The part of the PreTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (customize_id_token) {
...
   if (request_type == "authorization") {
...
     <code goes here>
...
   } else if (request_type == "access_token") {

 

Anywhere in this section of code can be used for requests to the '/authorize' endpoint. The '/authorize' endpoint correlates to a 'request_type' of 'authorization'.

The following is an example of saving an attribute from the ISAM Credential called 'emailAddress' which correlates to a Directory Server attribute of 'mail' :

...
if (customize_id_token) {
...
 if (request_type == "authorization") {
...
  // Beginning of custom code for '/authorize' endpoint
  // Saving attribute for '/token' endpoint
  // Collect the attribute from the STSUU and store in a variable
  var credMail = stsuu.getAttributeValueByName("emailAddress");

  // It's best practice to confirm whether values are null to prevent null pointers during execution
  // Adding the attribute to the STSUU as a 'claim:value' so it will be available at the '/token' endpoint
  if(credMail != null && credMail != "") {
   stsuu.addContextAttribute(new Attribute("credMail","urn:ibm:names:ITFIM:oidc:claim:value",credMail));
  }
  // End Saving Attribute for '/token' endpoint
  // Ending of custom code for '/authorize' endpoint
...
 } else if (request_type == "access_token") {

 

The key to storing the attribute is to add a 'Context' attribute to the STSUU of type 'urn:ibm:names:ITFIM:oidc:claim:value'.
This will make the attribute available at the '/token' endpoint with a type of 'urn:ibm:names:ITFIM:5.1:accessmanager' which is the default type of Access Manager attributes in the STSUniversalUser schema.

 

2) Token Phase : Retrieving Attributes and inserting into the JWT -- PreTokenGeneration


During a request to the '/token' endpoint data associated during the Authorization Phase is inserted into the STSUU document for use in customizing the JWT. In this phase, the attributes stored as context attributes in the Authorization Phase are available in the STSUU Attribute List (as opposed to Context Attribute List) for use.

The part of the PreTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (customize_id_token) {
...
 } else if (request_type == "access_token") {
...
  <insert_code_here>
  }

 

The following is an example of collecting the attribute stored in the Authorization Phase and adding it to the STSUU in such a way that it will be inserted into the JWT :

...
if (customize_id_token) {
...
 } else if (request_type == "access_token") {
...
  // Beginning of custom code for '/token' endpoint
  // Retrieving saved attribute at '/token' endpoint from the STSUU Attribute List
  var accessTokenMail = stsuu.getAttributeValueByName("credMail");

  // It's best practice to confirm whether values are null to prevent null pointers during execution
  // Inserting the attribute into the STSUU so that it will be inserted into the JWT :
  if(accessTokenMail != null && accessTokenMail != ""){
   stsuu.addAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("emailAddress", "urn:ibm:jwt:claim", accessTokenMail));
  }
  // Ending of custom code for '/token' endpoint
...
  }

 

The key to this phase is storing an Attribute in the Attribute List with a type of 'urn:ibm:jwt:claim'. The resultant JWT (ID Token) will have an attribute with the specified name and value.

You can validate the JWT to see whether the attribute is in the ID Token as expected by either manually base64 decoding the middle section of the JWT or using the tool at 'jwt.io'.

Scenario 2) OpenID Connect Federation using the Implicit flow

In this scenario, the end user will access the '/mga/sps/oauth/oauth20/authorize' endpoint with Parameters relative to their OpenID Connect context.

The user will be required to authenticate to the Reverse Proxy and this authenticated session will have information about the user such as email addresses, usernames, authentication methods, and other attributes that can be acquired via the Reverse Proxy or Attribute Sources.

After the user authenticates they may be prompted to authorize various access scopes and give consent to the app to access their data.

After successful consent, they will receive an authorization code that can be exchanged for an 'Access Token' and an 'ID Token'

 

1) Single Phase : Authorization Phase : Inserting attributes into the ID Token


During a request to the '/authorize' endpoint the user will be required to authenticate.
This gives the Advanced Access Control component access to attributes in the user's credential.

ISAM Reverse Proxy instances can be configured to retrieve attributes from LDAP as part of the authentication process. Reference to this will be included at the end of the document.

The part of the PreTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (customize_id_token) {
...
 if (populate_id_token || save_cred_attrs) {
...
  <code goes here>
...


 

Anywhere in this section of code can be used for requests to the '/authorize' endpoint. The '/authorize' endpoint correlates to a 'request_type' of 'authorization'.

During the implicit flow the ID Token is returned at the '/authorize' endpoint as opposed to the '/token' endpoint. The PreTokenGeneration mapping rules already have pre-built code that determines when this is the case. We'll use the provided logic instead of reinventing the wheel.

The following is an example of populating the ID Token with an attribute from the ISAM Credential called 'emailAddress' which correlates to a Directory Server attribute of 'mail' :

...
if (customize_id_token) {
...
 if (populate_id_token || save_cred_attrs) {
...   // Beginning of Custom Code for Implicit flow
  // Retrieving the attribute from the Attribute List of the STSUU   var credMail = stsuu.getAttributeValueByName("emailAddress");

  // It's best practice to confirm whether values are null to prevent null pointers during execution
  if(credMail != null && credMail != "") {
   // Inserting an attribute into the Attribute List of the STSUU
   stsuu.addAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("emailAddress", "urn:ibm:jwt:claim", credMail));
  }

  // End of Custom Code for Implicit Flow
...

 

The key to this phase is storing an Attribute in the Attribute List with a type of 'urn:ibm:jwt:claim'. The resultant JWT (ID Token) will have an attribute with the specified name and value.

You can validate the JWT to see whether the attribute is in the ID Token as expected by either manually base64 decoding the middle section of the JWT or using the tool at 'jwt.io'.

Scenario 3) Acquiring Attributes at the '/userinfo' endpoint for the Authorization Flow

In this scenario, an application may be accessing the '/mga/sps/oauth/oauth20/userinfo' endpoint to retrieve additional information about the user. The '/userinfo' endpoint may have more information about the end user that was not included in the 'ID Token'

The application is required to present an OAUTH 'Access Token' to authenticate to the '/userinfo' endpoint.

If the authentication is successful the application will receive a JSON response with attributes about the end user.

 

1) Authorization Phase : Storing the attributes in the PreTokenGeneration mapping rule


The part of the PreTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (customize_id_token) {
...
   if (request_type == "authorization") {
...
      <code goes here>
...
   } else if (request_type == "access_token") {

 

The following is an example of how to collect the 'emailAddress' attribute from the STSUU and store it for later use at the '/token' endpoint :

...
if (customize_id_token) { ...
 if (request_type == "authorization") {
...
  // Retrieving the value from the STSUU
  var credMail = stsuu.getAttributeValueByName("emailAddress");

  // It's best practice to confirm whether values are null to prevent null pointers during execution
  if(credMail !=null && credMail != ""){    // Saving attribute for '/token' endpoint
   stsuu.addContextAttribute(new Attribute("credMail","urn:ibm:names:ITFIM:oidc:claim:value",credMail));
  }   // End Saving Attribute for '/token' endpoint
...

 

2) Token Phase : Associating the attributes to the token in the PreTokenGeneration mapping rule


During this Phase we will be collecting the attribute we stored in Phase one. The attribute is now in the STSUU Attribute List with a type of 'urn:ibm:names:ITFIM:5.1:accessmanager' which is the default type of Access Manager attributes in the STSUniversalUser schema. The point of this phase is to use the OAUTH 2.0 Extra Attributes cache to store the attributes for use at the '/token' endpoint.

The part of the PreTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (customize_id_token) {
...
 } else if (request_type == "access_token") {
...
  <insert_code_here>
  }

 

The following is an example of how to store the 'credMail' attribute from the Authorization Phase into the OAUTH Token Extra Attributes Cache

...
if (customize_id_token) {
...
 } else if (request_type == "access_token") {
...
   if (token != null) {
    state_id = token.getStateId();
   }
  }
...
  // Beginning of custom code at the token endpoint
  // Retrieving saved attribute at '/token' endpoint and associating
  var accessTokenMail = stsuu.getAttributeValueByName("credMail");

  // It's best practice to confirm whether values are null to prevent null pointers during execution
  if(accessTokenMail != null && accessTokenMail.length() >0) {
   // Associating the Attribute with the Token for later use
   OAuthMappingExtUtils.associate(state_id,"credMail",accessTokenMail);
  }

  // Ending Retrieval of attribute and storage
  // Ending of custom code at the token endpoint

 

3) Userinfo Phase : Retrieving the attributes from the association and adding them to the response in the PostTokenGeneration mapping rule


During this Phase we will be using the OAUTH Mapping Utilities of 'getAssociation' to retrieve the stored value. This method relies on a 'state_id' parameter which can be acquired based on the presented 'Access Token'. Code is included to retrieve the State ID in such a way that null pointers will be avoided.

The part of the PostTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (request_type == "userinfo") {
...
 <code goes here>
...

 

The following is an example of how to return data at the '/userinfo' endpoint based on a Query Parameter called 'scope'
This assumes the the request URL has a format like : https://hostname.domain/mga/sps/oauth/oauth20/userinfo?scope=email

...
if (request_type == "userinfo") {
...
 var scope = stsuu.getContextAttributesAttributeContainer().getAttributeValueByName("scope");

 if(scope != null && scope.length() >0){

  if(scope.indexOf("email") >= -1) {
   var access_token = stsuu.getContextAttributesAttributeContainer().getAttributeValueByName("access_token");
   if(access_token != null && access_token.length() > 0){
    var oauth_token = OAuthMappingExtUtils.getToken(access_token);
    if(oauth_token != null){
     var state_id = oauth_token.getStateId();
    }
   }
   if(state_id !=null && state_id.length() > 0){
    // We have the state id, let's get the email from the association
    var email = OAuthMappingExtUtils.getAssociation(state_id,"credMail");
    if(email != null && email.length() >0){
     stsuu.addContextAttribute(new Attribute("mail","urn:ibm:names:ITFIM:oauth:response:attribute",email));
    }
   }
  }
 }
...

 

The response from the '/userinfo' will contain a JSON response with the attributes of name and value as specified in the mapping rule.

Scenario 4) Acquiring Attributes at the '/userinfo' endpoint for the Implicit Flow

In this scenario, an application may be accessing the '/mga/sps/oauth/oauth20/userinfo' endpoint to retrieve additional information about the user. The '/userinfo' endpoint may have more information about the end user that was not included in the 'ID Token'

The application is required to present an OAUTH 'Access Token' to authenticate to the '/userinfo' endpoint.

If the authentication is successful the application will receive a JSON response with attributes about the end user.

 

1) Authorization Phase : Associating the attributes to the token in the PostTokenGeneration mapping rule


Add the following code at the end of your PostTokenGeneration mapping rule.

This piece of code confirms that the 'oidc' attribute is equal to 'true'. This will be the case during an OIDC flow. It also confirms that the 'request_type' has a value of 'authorization'. This will be the case during an 'Implicit' flow. It then associates the Attribute value to the resultant 'Token' for retrieval at another time.

// Retrieve the 'oidc' attribute from the Context Attribute List
global_temp_attr = stsuu.getContextAttributes().getAttributeValueByNameAndType("oidc","urn:ibm:names:ITFIM:oauth:response:metadata");
// Confirm that the 'oidc' attribute is not null and is 'true', which means it's an OIDC flow.
if(global_temp_attr){

 // Confirm that we are at the '/authorize' endpoint
 if(request_type =="authorization") {

  // Retrieve the 'state_id' from the STSUU Context Attribute List
  var state_id = stsuu.getContextAttributes().getAttributeValueByNameAndType("state_id","urn:ibm:names:ITFIM:oauth:state");

  // Confirm that the 'state_id' value is not null and is not an empty string
  if(state_id !=null && state_id.length() > 0) {

   // Retrieve the 'emailAddress' attribute from the STSUU
   var accessTokenMail = stsuu.getAttributeValueByName("emailAddress");

   // Confirm that the value is not null or an empty string
   if(accessTokenMail != null && accessTokenMail != "") {

    // Associate the attribute value to the 'Token'
    OAuthMappingExtUtils.associate(state_id,"credMail",accessTokenMail);
   }
  }
 }
}


 

2) Userinfo Phase : Retrieving the attributes from the association and adding them to the response in the PostTokenGeneration mapping rule


During this Phase we will be using the OAUTH Mapping Utilities of 'getAssociation' to retrieve the stored value. This method relies on a 'state_id' parameter which can be acquired based on the presented 'Access Token'. Code is included to retrieve the State ID in such a way that null pointers will be avoided.

The part of the PostTokenGeneration Mapping Rule that we will be focusing on is as follows :

...
if (request_type == "userinfo") {
...
 <code goes here>
...

 

The following is an example of how to return data at the '/userinfo' endpoint based on a Query Parameter called 'scope'
This assumes the the request URL has a format like : https://hostname.domain/mga/sps/oauth/oauth20/userinfo?scope=email

...
if (request_type == "userinfo") {
...
 var scope = stsuu.getContextAttributesAttributeContainer().getAttributeValueByName("scope");

 if(scope != null && scope.length() >0){

  if(scope.indexOf("email") >= -1) {
   var access_token = stsuu.getContextAttributesAttributeContainer().getAttributeValueByName("access_token");
   if(access_token != null && access_token.length() > 0){
    var oauth_token = OAuthMappingExtUtils.getToken(access_token);
    if(oauth_token != null){
     var state_id = oauth_token.getStateId();
    }
   }
   if(state_id !=null && state_id.length() > 0){
    // We have the state id, let's get the email from the association
    var email = OAuthMappingExtUtils.getAssociation(state_id,"credMail");
    if(email != null && email.length() >0){
     stsuu.addContextAttribute(new Attribute("mail","urn:ibm:names:ITFIM:oauth:response:attribute",email));
    }
   }
  }
 }
...

 

The response from the '/userinfo' will contain a JSON response with the attributes of name and value as specified in the mapping rule.

[{"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Product":{"code":"SSQRZH","label":"IBM Security Access Manager Appliance"},"Component":"Advanced Access Control;AAC;Federation","Platform":[{"code":"PF004","label":"Appliance"}],"Version":"All Versions","Edition":"","Line of Business":{"code":"LOB24","label":"Security Software"}}]

Product Synonym

IBM Security Access Manager Advanced Access Control; ISAM AAC; IBM Security Access Manager Federation;ISAM Federation

Document Information

Modified date:
27 June 2019

UID

ibm10878999