ISAM OAuth Token Mapping Rules – Beginners Guide

The ISAM OAuth implementation is exceedingly flexible. If you are just looking to do basic OAuth flows, then chances are you won’t ever even look in these OAuth Mapping Rules. On the other hand, if you’re looking to modify the behaviour of the OAuth flow to achieve OAuth nirvana, then these Mapping Rules are going to be your best friend.

In the ISAM OAuth implementation, there are four main standard OAuth flows:

  • Resource Owner Password Credential
  • Authorization Code Flow
  • Implicit Grant
  • Client Credentials

In addition to this, there are a few extra flows, that we can use with ISAM’s Reverse Proxy:

  • OAuth Authentication
  • OAuth Session Endpoint

In each case, the OAuth flow passes through the API Protection Mapping Rules. For every ISAM OAuth definition, there are two mapping rules:

  • A Pre-Token Mapping Rule
    This mapping rule fires before ISAM has peformed token or supplied attribute validation.
    This means this rule is the right place to validate supplied credentials, such as username or password and can also be used for a variety of other mechanisms.
  • A Post-Token Mapping Rule
    This mapping rule fires after the tokens have been generated/exchanged. We also have the index into the OAuth token set that we can use to set and get attributes stored against the OAuth token set in key value pairs.

The mapping rules are all written in JavaScript, and as such, are very easy to follow if you keep them clean. However poorly thought out logic can quickly make them unwieldy, so be careful.

Here is a sample mapping rule, where I have removed all the sample logic supplied in the out of the box mapping rules, and simply described the plug points, and a few useful integrations commented in.

As of posting this, I haven’t run too many (READ: any) tests against this mapping rule. So use it first and foremost as a guide, and use it with caution in the short term. ūüôā

Sample Post Token Mapping Rule

importPackage(Packages.com.tivoli.am.fim.trustserver.sts);
importPackage(Packages.com.tivoli.am.fim.trustserver.sts.uuser);
importPackage(Packages.com.tivoli.am.rba.extensions);
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);

/** This is a sample ISAM OAuth Post Token Mapping rule.
 *  Authored by Philip Nye.
 *  For more useful ISAM OAuth information
 *  see https://www.philipnye.com
 */

/* To See the Trace Generated from this Request
 * Enable the Runtime trace String in AAC:
 * com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils=ALL
 */

IDMappingExtUtils.traceString("XXXXX: Entered PostToken Mapping Rule");
//Gather some useful attributes
var state_id = null;
var temp_attr = null;
var response_type = null;
var grant_type = null;
var request_type = null;
var username = null;

// The state id handle (The Immutable Identifier for the OAuth Grant)
// Note: This state_id isn't available in the PreToken Mapping rule because 
//         we haven't performed the token operations yet.
temp_attr = stsuu.getContextAttributes()
    .getAttributeValuesByNameAndType
        ("state_id", "urn:ibm:names:ITFIM:oauth:state");
if (temp_attr != null && temp_attr.length > 0)
{
    state_id = temp_attr[0];
}
//We can use the state_id to get and set attributes against the tokens. 


// The grant type
temp_attr = stsuu.getContextAttributes().getAttributeValuesByNameAndType
        ("grant_type", "urn:ibm:names:ITFIM:oauth:body:param");
if (temp_attr != null && temp_attr.length > 0) {
    grant_type = temp_attr[0];
}

// Get username
username = OAuthMappingExtUtils.getAssociation(state_id, "username");
if(username == null || username.length <= 0)
{
    temp_attr = stsuu.getContextAttributes().getAttributeValuesByNameAndType
             ("username", "urn:ibm:names:ITFIM:oauth:body:param");
    if (temp_attr != null && temp_attr.length > 0) {
        username = temp_attr[0];
        OAuthMappingExtUtils.associate(state_id, "username", username);
    }
}

/* The response type - since this can be either a POST or a GET, 
 * we need to check both.
 */
//If the response type is in the POST body, look here:
temp_attr = stsuu.getContextAttributes().getAttributeValuesByNameAndType
        ("response_type", "urn:ibm:names:ITFIM:oauth:body:param");
if (temp_attr != null && temp_attr.length > 0) 
{
    response_type = temp_attr[0];
}else 
{
    // Else look in the query string.
    temp_attr = stsuu.getContextAttributes().getAttributeValuesByNameAndType
            ("response_type", "urn:ibm:names:ITFIM:oauth:query:param");
    if (temp_attr != null && temp_attr.length > 0) 
    {
        response_type = temp_attr[0];
    }
}
IDMappingExtUtils.traceString("XXXXX: Detected: response_type="
    + response_type + " grant_type=" + grant_type);
// The request type - if none available assume 'resource'
temp_attr = stsuu.getContextAttributes().getAttributeValuesByNameAndType
        ("request_type", "urn:ibm:names:ITFIM:oauth:request");
if (temp_attr != null && temp_attr.length > 0) {
    request_type = temp_attr[0];
    IDMappingExtUtils.traceString
        ("XXXXX: Detected: request_type=" + request_type);
} else {
    // Set as resource if null.
    request_type = "resource";
}

if (request_type == "resource")
{
    
    IDMappingExtUtils.traceString("Post Token Map Rule: Resource Request");
    // The URL accessed for this validation is available at:
    var accessURL = stsuu.getContextAttributes()
        .getAttributeValueByNameAndType("path", 
            "urn:ibm:names:ITFIM:oauth:request");
    IDMappingExtUtils.traceString("URL of token validation: " + accessURL);
    
    /* This is the flow made when a token is being validated by an 
     * enforcement point. If you're using OAuth-Auth, you can use this 
     * section to add attributes to the ISAM Credential. 
     */
    //Add a custom attribute to the ISAM users credential
    stsuu.addContextAttribute(new Attribute("custom_attribute", 
            "urn:ibm:names:ITFIM:oauth:response:attribute", "some_value"));
            
    /* Add the value as a header in junctioned requests.
     * You need to set WebSEAL config item: 
     * force-tag-value-prefix = no
     * Or at least understand the ramifications. 
     */
    stsuu.addContextAttribute(new Attribute("tagvalue_always", 
       "urn:ibm:names:ITFIM:oauth:response:attribute", "custom_attribute"));
            
    //Set the credential Auth Level
    /* Note: This is an example of setting the authentication level
     *       for the OAuth-Auth session. This is a different attribute
     *       than that which is used with the session endpoint.
     *        When setting an Auth level, make sure you've configured
     *        WebSEAL to be aware of the existence of that level in the
     *        [authentication-levels] stanza. 
     */
    stsuu.addContextAttribute(new Attribute("AUTHENTICATION_LEVEL", 
             "urn:ibm:names:ITFIM:oauth:response:attribute", "4"));


    //Get a stored value:
    var genBy OAuthMappingExtUtils.getAssociation
        (state_id, "TokensGeneratedBy")
    //Add it to the response/cred.
    stsuu.addContextAttribute(new Attribute("TokensGeneratedBy", 
       "urn:ibm:names:ITFIM:oauth:response:attribute", genBy));
    
    
}else if (request_type == "authorization")
{
    if (response_type == "code")
    {
        //This is an Authorization Code Flow
        IDMappingExtUtils.traceString
            ("PostToken Map Rule: AZN Code Flow Request");
        
        /* This section might be used to validate scopes requested in an
         * AZN code flow. Or collect additional attributes submitted
         * on the consent page or from the authentication session credential
         * and store them with the grant.
         */
        
    } else if (response_type == "token")
    {
        //This is an Implicit Grant Flow
        IDMappingExtUtils.traceString
            ("PostToken Map Rule: Implicit Grant Request");
        
        /* This section might be used to validate scopes requested in an
         * Implicit flow. Or collect additional attributes from the 
         * authentication session credential and store them 
         * with the grant.
         */
        
        //Set Some Value against tokens:
        OAuthMappingExtUtils.associate(state_id, "TokensGeneratedBy", 
            "OAuth Implicit Grant");
        
    }
    
}else if (request_type == "access_token")
{
    if (grant_type == "authorization_code")
    {
        //This is a Token request using an Authorization Code.
        IDMappingExtUtils.traceString
             ("Post Token Map Rule: AZN Code Token Request");
        //Set Some Value against tokens:
        OAuthMappingExtUtils.associate(state_id, "TokensGeneratedBy", 
            "OAuth AZN Code Flow");

    }else if (grant_type == "password")
    {
        //This is an Resource Owner Password Credential Flow
        IDMappingExtUtils.traceString("PostToken Map Rule: ROPC Request");
        
        //Note: The Username and Password should have been validated 
        //      already, in the Pre Token Mapping rule. 
        
        /* This section can be used to collect attributes with the 
         * ROPC registration flow. This is the section that is most useful 
         * in Mobile Use cases.
         * Examples include:
         * - Electronic Device Fingerprint
         * - Trusteer Device Identifier
         * - A PIN code for subsequent 2nd factor auth with refreshes.
         * - A device friendly name     
         */
        //Get set PIN in inbound request
        var setPIN = stsuu.getContextAttributes().getAttributeValuesByNameAndType
                   ("setPIN", "urn:ibm:names:ITFIM:oauth:body:param");
        // Validate PIN Complexity here if required.

        //Set the PIN against tokens:
        OAuthMappingExtUtils.associate(state_id, "USER_PIN", setPIN);
        OAuthMappingExtUtils.associate(state_id, "USER_PIN_ATTEMPTS", "0");
        //In 9021 these attributes can be protected from USC manipulation:
        OAuthMappingExtUtils.associate(state_id, "USER_PIN", setPIN, true, true);
        OAuthMappingExtUtils.associate(state_id, "USER_PIN_ATTEMPTS", "0", true, true);
        //Set Some Value against tokens:
        OAuthMappingExtUtils.associate(state_id, "TokensGeneratedBy", 
            "OAuth ROPC");
        
         
    }else if (grant_type == "refresh_token")
    {
        //This is an Refresh Flow
        IDMappingExtUtils.traceString("PostToken Map Rule: Refresh Flow");
        
        /* This is any flow where the client is issued a refresh token.
         * This is where you might choose to validate an additional 
         * credential such as a PIN in order to complete the flow, 
         * or alternatively, modify an an attribute set against the 
         * token that dictactes its "Authentication" state. For example,
         * "PRE-PIN" vs. "POST-PIN" which each dictacting different API
         * access constraints. Or similarly, an ISAM Auth Level.
         */
        
        //Get PIN in inbound request
        var enteredPIN = stsuu.getContextAttributes().getAttributeValuesByNameAndType
                   ("PIN", "urn:ibm:names:ITFIM:oauth:body:param");
        var pinAttempts = OAuthMappingExtUtils.getAssociation(state_id, "USER_PIN_ATTEMPTS"); 
        OAuthMappingExtUtils.associate(state_id, "USER_PIN_ATTEMPTS", pinAttempts++);
        if (pinAttempts > 5)
        {
            //Too many attempts at PIN Validation.
            OAuthMappingExtUtils.deleteGrant(state_id);
            PluginUtils.logAuditEvent
                    (username,"PIN failed too manye times. Deleting Grant", false);
            OAuthMappingExtUtils.throwSTSUserMessageException
                    ("Too many attempts at PIN validation");
            
            
        }else
        {
            if(OAuthMappingExtUtils.getAssociation(state_id, "USER_PIN") == enteredPIN) {
                // Pin authentication successful, set next_uri
                PluginUtils.logAuditEvent(username,"PIN authentication successful on Refresh", true);
                stsuu.addContextAttribute(new Attribute
                      ("next_uri", "urn:ibm:names:ITFIM:oauth:response:attribute", "authenticated"));
                //An additional attribute could be used here to store the auth state. 
                //This is useful if you want to have a PRE-PIN state or to handle 
                //PIN auth failures more gracefully.
                OAuthMappingExtUtils.associate(state_id, "STORED_STATE", "PIN_AUTH");
                OAuthMappingExtUtils.associate(state_id, "USER_PIN_ATTEMPTS", "0");
            }
            else
            {
                // The pin entered by the user was not stored against the StateID
                // Pin authentication failed update state and set next_uri
                PluginUtils.logAuditEvent(username,"PIN authentication unsuccessful on Refresh", false);
                IDMappingExtUtils.traceString("Set Stored State: AUTH_PRE-PIN");
                stsuu.addContextAttribute(new Attribute
                      ("next_uri", "urn:ibm:names:ITFIM:oauth:response:attribute", "prepin"));
                OAuthMappingExtUtils.associate(state_id, "STORED_STATE", "AUTH_PRE-PIN");
                
            }
         
         }
        
    }else if (grant_type == "client_credentials")
    {
        //This is an Client Credentials Flow
        IDMappingExtUtils.traceString
            ("PostToken Map Rule: Client Credential Request");
        
        //Set Some Value against tokens:
        OAuthMappingExtUtils.associate(state_id, "TokensGeneratedBy", 
            "OAuth Client Credentials");
    }
    
} else if (request_type == "session")
{
    var stsuuAttrs = stsuu.getAttributeContainer();
    /* Add attribute "authenticatedBy" 
     * with value "OAuth Session Endpoint"
     * to the current user credential.
     */
     stsuuAttrs.setAttribute(new Attribute("authenticatedBy", 
         null, "OAuth Session Endpoint"));
 
     /* Note: This is an example of setting the authentication level
      * for the established session. This is a different attribute
      * attribute than that which is used to do the same with
      * OAuth-Auth as a resource request.
      * When setting an Auth level, make sure you've configured
      * WebSEAL to be aware of the existence of the level. 
      */
     stsuuAttrs.setAttribute(new Attribute("AUTHENTICATION_LEVEL", 
         "urn:ibm:names:ITFIM:5.1:accessmanager", "2"));
 
     /* To set the EAI Redirect Header use the following:
      * To ensure it takes priority, set the Webseal Flag:
      * eai-redir-url-priority=yes 
      */
     var redir = new Attribute("itfim_override_targeturl_attr",
         "urn:ibm:names:ITFIM:5.1:accessmanager", "/somepage.jsp");
     stsuu.addAttribute(redir);
}

 

4 thoughts on “ISAM OAuth Token Mapping Rules – Beginners Guide

Add yours

  1. I am testing api protection and access my test resource using an oauth access token (Bearer token in the request header). It seems that the mapping rules get executed only during the first time I access the resource ? The subsequent calls don’t seem to cause any log lines to be written to the trace.log

    Is this the correct behaviour ?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

WordPress.com.

Up ↑

%d bloggers like this: