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); }
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 ?
LikeLike
Hi,
Yep, it is, see this post for a bit more detail: https://philipnye.com/2014/07/29/isam-for-web-and-mobile-oauth-authentication-and-sessions/
LikeLike
Hi Phil,
Went through your article. Is there any efficient way of using “OAuthMappingExtUtils.associate()”.
In my post mapping rule, i am using OAuthMappingExtUtils.associate() multiple times to associate various values against the state_id. I believe every associate calls open a db connection . Using associate multiple times can slow the response time .
Can we club multiple association call with a single one ?
For e.g. Instead of using below :
OAuthMappingExtUtils.associate(state_id, “applicationName”, applicationName);
OAuthMappingExtUtils.associate(state_id, “client_id”, client_id);
OAuthMappingExtUtils.associate(state_id, “mac_secret”, mac_secret);
use something like below :
OAuthMappingExtUtils.associate(state_id, “data”, JSON.stringify({
applicationName:applicationName,
client_id:client_id,
mac_secret:mac_secret
}));
Will you try and let us know if this is possible?
Regards,
Ajay
LikeLike
(I know we’ve spoken out of band, but putting this here for completeness)
As of 905, we have a group mechanism for associates:
https://www.ibm.com/support/knowledgecenter/SSPREK_9.0.5/com.ibm.isam.doc/productoverview/concept/whats_new.html
Batch commands for grant association commands
The OauthMappingExtUtils class is enhanced to allow several associated attributes to be managed at once. This class includes batch operations for create, retrieve, update, and delete. See the OauthMappingExtUtils class in the Javadoc that is embedded in the appliance for details.
LikeLike