OAuth providers often provide extended functionality to clients, depending on individual requirements. This extended functionality often requires additional information to be stored with an OAuth grant.
This article is going into how ISAM allows you to store additional information and metadata against an OAuth grant. The number of scenarios which can be targeted with this capability are numerous, this is a powerful and flexible feature.
OAuth grants and state ID
In OAuth, there are two tokens we’re very familiar with; the access token and refresh token. The act of obtaining an access token or refresh token is known as a grant. Access tokens are usually short lived and expire, with a refresh token being used to get a new access token. When a refresh token is used, both a new access and refresh token are created, rolling over the token values. Due to the rolling nature of these tokens, there is the need for an additional ID. This ID will be the same for both an access and refresh token, and will not change when a refresh token flow is performed and new tokens are issued. This ID is known as the state ID, which identifies a particular OAuth grant for its lifetime.

This state ID is used heavily when managing extra attributes, as it is the only consistent identifier for a given OAuth grant.
The state ID gets issued when the initial grant is created. So it is available in most OAuth use cases.
Mapping Rules
OAuth extra attributes are managed and used in mapping rules. These occur at two well defined locations during OAuth requests:
Pre-token – The “before” mapping rule, runs before the primary operation of the request such as issuing tokens. Hence the name “pre-token”
Post-token – The “after” mapping rule, runs after the primary operation of the request. This rule usually has the state-id pre-populated and available.
There are some instances when a state_id
does not yet exist for a grant during the pre-token invocations. This occurs on a request to /authorize
or on a request to /token
when doing the ROPC grant type, as no tokens exist yet. Information available in that rule can still be saved against a grant, it just needs to be stored in the stsuu
to be retrieved and associated against the grant in the post-token rule of the same request.
While the pre and post token rules are the primary mapping rules in which extra token attributes are used they can also be used in an infomap, however the state_id
will have to be manually retrieved.
Mapping a token to a state ID
In mapping rules, its possible to retrieve a token and the information associated with it using the call
OAuthMappingExtUtils.getToken(tokenString);
This will return a token object which contains all the information about that token. Including:
- Lifetime
- Username
- Client ID
- Scope
- State Id
state_id
is the focus here. The following snippet shows getting a state_id
from a token:
var token = OAuthMappingExtUtils.getToken(tokenString);
if(token != null) {
var state_id = token.getStateId();
}
This is very useful in the pre-token rule, where the state_id isn’t automatically populated, or in an infomap, where the OAuth token has been provided via other means (eg posted as a parameter, present in a header, or in the users credential).
Attribute operations
Now that we’ve explored were a grants attributes can be used and how to get the state_id
, we can look at the actual calls which can be made in JavaScript to work with attributes.
There are two classes of operations, single attribute operations, and batch operations. Batch operations were added in 9.0.5.0 and are essential when working with multiple attributes, as they are significantly more efficient with database transactions (one per operation, rather than one per attribute).
Save or update a single attribute with:
OAuthMappingExtUtils.associate(state_id, "attribute_key", "attribute_value");
Remove a single attribute from a grant this will return the value removed if any:
var removed_value = OAuthMappingExtUtils.disassociate(state_id, "attribute_key");
Retrieve a list of all association keys, retrieveAllAssociations
is usually more useful, as it fetches the value at the same time – returns a java array of strings
var keys = OAuthMappingExtUtils.getAssociationKeys(state_id);
for(var i = 0 ; i < keys.length; i++) {
var key = keys[i];
//Perform operations with each key here
}
Get a specific token attribute – returns a string / null if no value found
var attribute_value = OAuthMappingExtUtils.getAssociation(state_id, "attribute_key");
Get all of the associations – returns a java Map
var attributes = OAuthMappingExtUtils.retrieveAllAssociations(state_id);
var keys = attributes.keySet().toArray();
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = attributes.get(key);
// Do operation with key + value here
}
Add several attributes – will not update existing attributes
var to_add = OAuthMappingExtUtils.getEmptyMap();
to_add.put("attribute1","value1");
to_add.put("attribute2","value2");
OAuthMappingExtUtils.batchCreate(state_id, to_add)
Update several attributes – will not create attributes
var to_update = OAuthMappingExtUtils.getEmptyMap();
to_update.put("attribute1","new_value1");
to_update.put("attribute2","new_value2");
OAuthMappingExtUtils.batchUpdate(String stateID,Map<String, String> attributesToUpdate)
Delete several attributes
var to_delete = new ArrayList();
to_delete.add("attribute1");
to_delete.add("attribute2");
OAuthMappingExtUtils.batchDelete(state_id, to_delete);
It should be noted that attributes do have some limitations, the maximum attribute key or value length is 256. If bigger things need to be stored they must either go in the IDMappingExtCache
, or be split up and stored in multiple attributes.
All of the methods which are mentioned here are documented in javadoc which can be downloaded from the appliance.
A common pattern, sharing state from /authorize to /token
One of the most common patterns seen, is the need to share some information between the /authorize
and /token
endpoints. In a browser based OAuth flow only /authorize
will ever have access to the users session, and thus their credential attributes. Token attributes are very useful for saving that value at the /authorize
step and then retrieving them at /token
. This can be used to add claims to a JWT, or to return additional values in the bearer token which is returned to the client.
The following snippet saves the users authentication level at /authorize
, and returns them at /token
. It should be added to the post token rule:
if(request_type == "authorization") { // Request to /authorize
// Get the attribute value to save.
var to_save = stsuu.getAttributeContainer().getAttributeValueByName("AUTHENTICATION_LEVEL");
// Save the attribute
OAuthMappingExtUtils.associate(state_id, "authentication_level", to_save);
} else if(request_type == "access_token") { // Request to /token
// Retrieve the value
var lvl = OAuthMappingExtUtils.getAssociation(state_id, "authentication_level");
// Return it in the bearer token
stsuu.addContextAttribute(new com.tivoli.am.fim.trustserver.sts.uuser.Attribute("AUTHENTICATION_LEVEL" ,"urn:ibm:names:ITFIM:oauth:response:attribute", lvl));
}
Or, if you need the credential attribute in the pre-token rule of an authorization code flow, often needed when populating an id_token, use this snippet in the pre-token rule:
if (request_type == "access_token" && grant_type == "authorization_code") { // Request to /token, when performing the authorization code flow
// We need a token value to lookup, get the token string. We know that it MUST be posted in the authorization code flow
var code = stsuu.getContextAttributes().getAttributeValueByNameAndType("code", "urn:ibm:names:ITFIM:oauth:body:param");
// Use the code string to get a token object - which contains a state_id
var token = OAuthMappingExtUtils.getToken(code);
// Double check we got a token successfully.
if(token != null) {
// Read the state_id
var state_id = token.getStateId();
// Use the state_id to retrieve the value.
var groups = OAuthMappingExtUtils.getAssociation(state_id,"AUTHENTICATION_LEVEL");
Sharing state isn’t limited to sharing between /authorize
and /token
. The same associated attribute can be used in requests to /userinfo
, /introspect
and /session
.
#ISAM