I have a requirement to add a user’s roles (provisioning roles) as a custom claim to the ID token or access token. I have implemented this via a custom OIDC claims script, but it was really much more involved than I would have thought. So before I submit this as the preferred solution, I’d like to check that I’m not missing a simpler method.
The main issue seems to be that the OAuth2 customization script environments are all lacking the ‘openidm’ binding. I can lookup the UUIDs of the roles using eg the fr-idm-effectiveRole attribute, but to get the name of the role it seems like you have to call out to IDM to get it. That involves a lot of steps due to the lack of a binding in these scripts:
- Create a service account and grant it the fr:idm:* permission.
- Create an ESV variable for the SA id and a secret for the SA private key JWK.
- Load those ESVs in the script and use the FR JOSE library to parse the JWK private key.
- Create a signed JWT using the private key and call AM using the jwt-bearer grant type to obtain an access token.
- Use the httpClient to call IDM’s managed object endpoint using the access token.
This ends up being quite a lot of code! (I’d also feel happier if I could restrict that service account to read-only perms on managed users/roles).
I can share code if anyone else is solving the same problem. So, does anyone know a simpler way?
In case anyone is interested, this is the core part of the code I am currently using (some imports and other details omitted):
function getIdmAccessToken() {
logger.message('Generating JWT to obtain IDM access token');
var idmServiceAccountID = systemEnv.getProperty('esv.idm.script.service.account.id');
var idmServiceAccountKey = systemEnv.getProperty('esv.idm.script.client.private.key');
if (!idmServiceAccountKey) {
logger.error('Unable to get IDM Service Account private key');
return;
}
var amHost = requestProperties.get('requestUri').getHost();
var endpoint = 'https://' + amHost + ':443/am/oauth2/access_token';
var jwk = frJava.RsaJWK.parse(idmServiceAccountKey);
var handler = new frJava.SigningManager().newSigningHandler(jwk);
var jwtBuilder = new frJava.JwtBuilderFactory();
var claims = new frJava.JwtClaimsSet();
claims.setIssuer(idmServiceAccountID);
claims.setSubject(idmServiceAccountID);
claims.addAudience(endpoint);
claims.setClaim("exp", new Date().getTime() / 1000 + 180);
claims.setJwtId(java.util.UUID.randomUUID().toString());
var jwt = jwtBuilder.jws(handler)
.headers().header("alg", "RS256").done()
.claims(claims)
.build();
logger.message('Generated JWT: {}...', jwt.substring(0, 20));
var request = new http.Request()
.setUri(endpoint)
.setMethod('POST');
var body = new http.Form();
body.add('client_id', 'service-account');
body.add('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
body.add('assertion', jwt);
body.add('scope', 'fr:idm:*');
request.setEntity(body);
var response = httpClient.send(request).get();
logger.message('Authentication response: {}', response.getStatus());
if (response.getStatus().getCode() !== 200) {
return;
}
var json = JSON.parse(response.getEntity().getString());
var accessToken = json.access_token;
logger.message('Got access token: {}...', accessToken.substring(0, 5));
return accessToken;
}
var cachedUserData;
function loadUserData(fields) {
if (cachedUserData) {
return cachedUserData;
}
if (identity) {
var token = getIdmAccessToken();
var uuid = identity.getAttribute('fr-idm-uuid').toArray()[0];
var realm = requestProperties.get('realm');
var request = new http.Request()
.setMethod('GET')
.setUri('http://idm/openidm/managed' + realm + '_user/' + uuid +
'?_fields=' + fields);
request.getHeaders().add('Authorization', 'Bearer ' + token);
request.getHeaders().add('Accept-API-Version', 'resource=1.0,protocol=1.0');
var response = httpClient.send(request).get();
logger.message('Called IDM to get user information: {}', response.getStatus());
if (response.getStatus().getCode() !== 200) {
logger.error('Bad response: {}', response.getEntity().getString());
return;
}
cachedUserData = JSON.parse(response.getEntity().getString());
logger.message('User data: {}', JSON.stringify(cachedUserData));
}
return cachedUserData;
}
function getUserRoles() {
function resolveClaim(claim) {
var roles = [];
var data = loadUserData('roles/*_ref/name,effectiveAssignments/*_ref/name');
for (var i = 0; i < data.roles.length; ++i) {
roles.push(data.roles[i].name);
}
return roles;
}
return resolveClaim;
}
1 Like