Discussion
Pegasystems Inc.
JP
Last activity: 7 Nov 2024 12:54 EST
How to map nested JSON object with OpenID Connect
Hi,
My customer reported that Pega Platform is unable to map nested JSON object with OpenID Connect based Single Sign On. In this post, I will share how we can achieve this requirement.
- Issue
In my experience, most of OpenID Provider send attributes as top level in JSON object. However, some software sends these as nested JSON object. For example, below are the sample claims that are returned from Okta's UserInfo endpoint. You can find that "street_address", "locality", "region", and "postal_code" are nested under "address" attribute.
Customer created corresponding address related properties under Data-Admin-Operator-ID class and tried to map returned nested attributes to Pega properties using mapping tab in Authentication Service rule as below. However, this approach is not valid.
- Resolution
As of today, current Pega Platform can't handle nested JSON object in the standard Authentication Service rule and I've submitted an enhancement request (FDBK-94722). As a workaround, create a post-authentication activity in which you can access JSON claims directly by Java. You must include below snippet.
tools.getRequestor().getRequestorPage().putString("pyAuthenticationPolicyResult", "true");
Sample Code:
1. Create a post-authentication activity
2. In the first step, write Java code as below to retrieve address claims.
Object dp = tools.findPage("D_pzSSOAttributes").getProperty("pyAttrList").getObjectValue();
com.fasterxml.jackson.databind.ObjectMapper om = new com.fasterxml.jackson.databind.ObjectMapper();
try{
Map<String,Object> props = om.convertValue(dp, Map.class);
Map<String,Object> address = (Map<String, Object>) props.get("address");
if(address!=null){
street_address = address.get("street_address").toString();
locality = address.get("locality").toString();
region = address.get("region").toString();
postal_code = address.get("postal_code").toString();
//oLog.infoForced("street_address: " + street_address);
//oLog.infoForced("locality: " + locality);
//oLog.infoForced("region: " + region);
//oLog.infoForced("postal_code: " + postal_code);
ClipboardPage opr = tools.findPage("OperatorID");
//oLog.infoForced("OperatorID.StreetAddress: " + opr.getString("StreetAddress"));
//oLog.infoForced("OperatorID.City: " + opr.getString("City"));
//oLog.infoForced("OperatorID.Prefecture: " + opr.getString("Prefecture"));
//oLog.infoForced("OperatorID.PostalCode: " + opr.getString("PostalCode"));
String[][] param = {
{street_address,opr.getString("StreetAddress")},
{locality,opr.getString("City")},
{region,opr.getString("Prefecture")},
{postal_code,opr.getString("PostalCode")}
};
for(int i=0; i<param.length; i++){
//oLog.infoForced(param[i][0] + " " + param[i][1]);
if(param[i][0].equals(param[i][1])) {
isUpdated = false;
} else {
isUpdated = true;
break;
}
}
} else {
oLog.infoForced("No address found");
}
} catch (Exception e) {
e.printStackTrace();
}
tools.getRequestor().getRequestorPage().putString("pyAuthenticationPolicyResult", "true");
3. In the following steps, complete the activity such way that if there are any changes in attributes, update the operator ID with new values. If none, exit the activity.
Steps:
Pages & Classes
4. Next, add ARO (Access of Role to Object) to the Access Role for the logging in user. Specify CanAccessSelf Access When rule for Read instances and Write instances.
5. That's it. Address information is now properly stored in Operator ID after SSO process is completed.
* For other top level JSON objects, you can still use Mapping tab in Authentication Service rule (see below screenshot). Only nested JSON object should be handled by post-authentication activity.
- Note
I have also uploaded a document about how to build OpenID Connect SSO with Okta in a separate post. Please see https://support.pega.com/discussion/how-build-sso-oidc-openid-connect.
Hope this helps.
Thanks,