How to Access OIDC Claims and OAuth Tokens During Operator Provisioning in Pega

OIDC Authentication - Model Operator Identification Based on ID Token Claims & Access Token Availability

Hi Team,

We are currently migrating our application from Pega On-Premises to Pega Cloud and replacing our existing LDAP authentication with OpenID Connect (OIDC) authentication.

Requirement

Our Identity Provider (IdP) returns the user’s AD Groups as part of the ID Token claims.

Our requirements are:

  1. Model Operator Identification

    • During operator provisioning, identify the appropriate Model Operator based on the ad_groups claim received in the ID Token.

    • Create the operator profile using the identified Model Operator.

    • For existing users, always assign/update the appropriate Access Group during login in case the user’s AD group membership has changed.

  2. Token Availability

    • After successful authentication, the Access Token and Refresh Token received from the IdP should be available within the user session so they can be used for downstream REST API calls.

Current Status

OIDC authentication is working successfully.

The ID Token is validated successfully, and the debug logs confirm that all expected claims are received.

Example debug logs:

StateParam Validation is successful
- Fetching access token using authCode received
- Successfully fetched access token and ID token using authCode
- Validating ID token received from access token endpoint
- JWT is Signed
- Truststore picked is MyOrg-jwt-signing-test-2025-08-27
- Successfully validated ID token with standard claims

UserClaims received:
{
  sub=user1,
  ad_groups=[
      KB00-MyAPPSuperuserVer,
      KB00-MyAPPSuperuser,
      KB00-MyAPPSuperuserTest
  ],
  ...
}

So, the IdP is returning the expected claims, and Pega is successfully validating the ID Token.


Challenges

1. Accessing OIDC Claims During Operator Provisioning

Our goal is to determine the appropriate Model Operator based on the ad_groups claim.

We tried the following:

  • Mapped the ad_groups claim to a temporary page using Claim Mapping.

  • Attempted to reference that page from the Operator Provisioning Data Transform.

However, the mapped page is not available during provisioning.

We also referred to the Pega documentation, which mentions that D_pzSSOAttributes can be used to access SSO claims.

However:

  • D_pzSSOAttributes is not available during operator provisioning.

  • We are unable to access pyAttrList or any of the claims from that data page.

Question 1

Is there any supported approach to access OIDC claims (especially ad_groups) during “Enable operator provisioning using model operator”?

More specifically:

  • At what stage are the OIDC claims available?

  • Is there a supported API, data page, clipboard page, or extension point that can be used to read these claims before the Model Operator is selected?

  • Has anyone implemented dynamic Model Operator selection based on OIDC claims such as AD Groups?


2. Access Token and Refresh Token Availability

The debug logs clearly indicate that Pega successfully retrieves the Access Token.

However, we are unable to locate it:

  • Not on the Clipboard.

  • Not in any token-related instances.

  • Not in any obvious requestor/session pages.

Question 2

Where does Pega store the Access Token and Refresh Token after successful OIDC authentication?

Ideally, we would like to access these tokens during PostAuthentication (or another supported extension point) so they can be used for downstream API calls on behalf of the authenticated user.

Is there a supported mechanism or API to retrieve these tokens?


If we are missing any configuration or following an incorrect approach, please let us know.

We have attached our OIDC configuration screenshots document for reference.

Any guidance or implementation experience would be greatly appreciated.

Thank you!

Pega_OIDC_Config.docx (302.1 KB)

For Question 1:

While using OIDC claims (ad_groups) for model operator & access groups, the key point is you can drive operator provisioning from OIDC claims, but you shouldn’t rely on D_pzSSOAttributes during provisioning. Instead, you use one of the below

  • direct claim mapping with the curly‑brace syntax in the provisioning data transform, or
  • a requestor‑scope data page invoked in that data transform that reads the claims.

During OIDC authentication pega validates the ID token and exposes claims for mapping in the Auth Service configuration. In operator provisioning, you can reference those mapped claims directly in the “Enable operator provisioning using model operator” configuration

  • via expression (for the Model operator field) or
  • via data transform (Class Name = Data-Admin-Operator-ID).

The supported way to read claims in a provisioning data transform is to use the curly‑brace notation:

OperatorID.pyAccessGroupsAdditional = {ad_groups}
OperatorID.pyAccessGroup = {ad_groups[0]}
OperatorID.pyOrganization = {org}
OperatorID.pyUserIdentifier = {sub}

You can then take {ad_groups}, apply your own precedence rule (e.g. pick first matching group from an allowed list), derive a Pega access group or model operator ID from that and set .pyAccessGroup, .pyAccessGroupsAdditional, or even the model operator name. If you need more complex logic (mapping multiple AD groups to different model operators) then define a requestor‑scope data page whose source is a data transform or activity, inside that, read the OIDC claims via {claimName} or by looking at your mapping, then have your data transform compute the model operator and access groups.

For first‑time provisioning, you use the operator provisioning data transform to pick the model operator based on {ad_groups}, set .pyAccessGroup and .pyAccessGroupsAdditional accordingly. For subsequent logins, you use a Post‑Authentication Activity to re‑read the claims, adjust access groups if the user’s AD group membership changed (for example, remove/add access groups based on current {ad_groups}).

So, yes there is a supported way to access OIDC claims during provisioning and it is to use the claim‑reference syntax ({claimName} and, for arrays, {ad_groups[0]}) inside the provisioning data transform or expression, rather than relying on D_pzSSOAttributes being populated at that exact moment.

And for Access/ Refresh tokens, for OIDC, the access token is persisted as an instance of Data-Admin-Security-OAuth2-AccessToken (and related token key classes) and stored in PR_DATA_TOKEN (and related tables). You can see this in the DB and through classes such as Data-Admin-Security-AccessTokenKeys. Pega also exposes the Access Token to the clipboard during authentication via internal APIs, which you can reach from a Post‑Authentication Activity. In Post-Authentication Activity use OOTB activity GetOperatorAccessToken or similar
to retrieve the current Access Token for the authenticated operator.

For Question 2:

And for Refresh token, there is no straight OOTB way.
Pega handles token refresh for its own OAuth clients, but retrieving the raw Refresh Token from an external IdP and using it directly inside Pega is not fully exposed, and several posts point out that refresh handling may need custom logic or separate calls to the IdP

So for downstream REST calls on behalf of the user, use the Access Token you can retrieve via Post‑Authentication Activity and token management classes/activities. Have a strategy to refresh or re‑obtain tokens if they expire either by calling the IdP’s token endpoint (custom logic) or by using Pega’s token revocation/renewal patterns (pxRevokeToken + new OAuth request).

I don’t think the claims can be referenced directly (for example, {sub} or {ad_groups}) in the Operator Provisioning Data Transform, as they appear to be treated as literal text rather than being resolved to the corresponding claim values. I also tried defining parameters in the Data Transform and passing the claim values, but that approach did not work.

Therefore, I’m looking for a supported way to access the OIDC claims in the context of “Enable operator provisioning using model operator.” Is there any supported mechanism to read the ID Token claims at this stage so that business logic can be applied to identify the appropriate Model Operator based on the ad_groups claim?

You are right that claims are not automatically substituted inside the operator‑provisioning DT the way they are in some SAML/OIDC mapping contexts. In practice, {sub} or {ad_groups} will often be treated as literal text unless the engine specifically injects those values into the mapping at that point, which it doesn’t seem to do for the “Enable operator provisioning using model operator” DT.

Given that, there is no simple, supported API that lets you read the full ID token, inspect arbitrary claims like ad_groups and pick a model operator inside the provisioning DT before the operator is created, you can try one of the below appoaches-

approach #1: Use a static or simple expression for the model operator, then adjust access groups and attributes later.
Use a single model operator (or a small set) for provisioning, focusing on getting the operator record created successfully.
In a Post‑Authentication Activity, where you do have better access to OIDC data (and to tables like PR_DATA_TOKEN / token classes), apply your business logic based on ad_groups and update- pyAccessGroup, pyAccessGroupsAdditional and any other operator attributes you want to drive from group membership.

approach #2: Drive business logic from mapped attributes, not the raw claims.
Map key claims to operator properties via the authentication service (for example, map ad_groups into a simple text or value list on the operator). Use those mapped properties inside the provisioning DT to make simpler decisions (e.g. choose between a small set of model operators based on a single mapped “role” or “tenant” value), rather than trying to parse the original OIDC claim array directly.

Remember that there is no clean, supported mechanism today to read and apply ID token claims directly inside the operator provisioning data transform to select a model operator. Claims are validated and available to the engine but not exposed in a way that the provisioning DT can reliably consume as variables. You need to implement a custom solution here to provision from a fixed model operator and then use Post‑Authentication (where you can more easily reach token/claim information)

Thank you for your prompt response.

Due to our organization’s strict security policies, we are required to adhere to the Principle of Least Privilege (PoLP) during the initial operator provisioning process. As a result, assigning a generic or overly permissive Model Operator—even temporarily before updating it in Post-Authentication—is considered a compliance concern. This is why we are trying to resolve the user’s ad_groups as early as possible, ideally before the operator is provisioned.

To achieve this, we explored several approaches based on the available OIDC configuration options in Pega.

1. Data Page Reference in the Model Operator Field

The tooltip for the Model Operator field states:

“Use a Data Page reference to write custom logic that reads OIDC attributes from D_pzSSOAttributes.”

However, in practice, D_pzSSOAttributes does not appear to be fully populated on the thread during the Model Operator evaluation step—and not even available for Post-Authentication activity and it gets only populated on the clipboard once authentication is done.

We also tried passing parameters directly in the Model Operator field under “Set operator context using”:, for example:

D_GetModelOperator[sub:{sub}, ADGroup:{ad_groups}].pyModelName

This works correctly for scalar claims such as {sub}. However, it fails for array-based claims such as {ad_groups}, because Pega attempts to perform a strict String conversion on the parameter, whereas our IdP sends ad_groups as a JSON array. Our identity team is not willing to change the token format by converting the array into a comma-separated string.

This approach doesn’t work because the Authentication Service passes only scalar claims. Array claims (ArrayList) are not supported.

2. OIDC Claim Mapping

We attempted to map {ad_groups} to a custom Value List property on the OperatorID page class via the Mapping tab. However, as noted, during the execution of the Model Operator provisioning rules, those mapped properties are not yet available on the thread.

It seems that the claim mapping in the Authentication Service rule is executed after the Model Operator provisioning step.

Questions Regarding the Post-Authentication Approach

If there is no supported mechanism to consume array claims before operator provisioning, our only remaining option may be to request an internal security exception. In that case, we would provision the operator with a minimal-privilege fallback Model Operator and perform the final authorization during Post-Authentication.

Before pursuing that approach, we would appreciate clarification on the following security considerations:

  1. If the operator is provisioned using a least-privilege Model Operator, but the Post-Authentication activity determines that the user’s ad_groups are not authorized and rejects the login, is terminating the requestor at that stage considered a secure and supported pattern from Pega’s perspective?

  2. If authentication is rejected during Post-Authentication, is the Operator record still created, or is provisioning rolled back?

  3. After the authentication is rejected, is there any possibility of residual session state, clipboard data, or other artifacts remaining that could present a security risk?

Thanks,

Brahmesh.

You’re running into two separate limitations at once:

  • OIDC array claims (like ad_groups) are not exposed early enough or in a usable form for model‑operator selection.
  • Operator provisioning and claim mapping are ordered so that you don’t have a clean “read all claims, then pick a model operator” hook.

Given that, using a least‑privilege model operator + Post‑Authentication hard‑fail is not just a workaround, it’s realistically the only guardrail‑friendly option right now. :slight_smile:

As we have an initial understanding of what the current implementation can/ cannot do:

  • The Model Operator logic can see scalar claims passed as parameters, but array claims (like ad_groups) are not supported – the engine tries to treat them as simple strings, and there’s no first‑class way to work with them before provisioning.

  • D_pzSSOAttributes and claim mappings are not reliably populated at the point where the model operator is chosen; they are available only later in the flow (and in your testing, not even consistently in Post‑Authentication).

So at the moment there is no fully supported mechanism to Consume array‑type OIDC claims during the ‘Enable operator provisioning using model operator’ step, apply business logic, and select a model operator purely from those claims.

considering your POLP compliance, the approach would be -

  • Provision from a strictly least‑privilege model operator.
    • This model operator should have the minimum access group(s) and privileges required to allow a login attempt to complete and reach Post‑Authentication.
  • Perform full authorization in Post‑Authentication.
    • In Post‑Authentication, you now have a stable context to read the claim mapping or token information, process ad_groups (even if you need a custom data page or activity to parse an array) and decide whether the user is allowed any higher privilege. If they are, you can update pyAccessGroup / pyAccessGroupsAdditional or even redirect them to a different portal.
  • If not authorized, hard‑fail and terminate the requestor.
    • It is a supported and secure pattern to deny access in Post‑Authentication, log the reason and explicitly terminate the requestor (via TerminateRequestor or by throwing an auth failure that prevents any portal from being displayed).

In this scenario:

the operator record may have been created (from the least‑priv model operator), but it has only minimal privileges and no active portal session. the requestor and clipboard are torn down. they are in memory only for that request and do not persist as a usable session.

Q1: From Pega’s perspective, Post‑Authentication is exactly where you are expected to run final authorization checks, and it is valid to deny access and terminate the requestor if the user’s claims do not match your policy. As long as the fallback model operator is truly least‑privilege, you’re not granting meaningful access before you make this decision.

Q2: If this is a first‑time login, the operator record created from the fallback model operator will still exist, but you can treat it as a dormant/minimal profile or implement a cleanup process for operators that never receive a valid authorization. Also ensure provisioning is not automatically “rolled back” but no portal session is started, and no elevated access group is assigned.

Q3: Once you terminate the requestor (or the login fails before a portal is rendered), Pega discards clipboard with the HTTP session. There will be no ongoing authenticated session to exploit. The only persistent artifact is the operator record, which you should be created with least privileges. If even that is a concern, you can implement a periodic cleanup for operators that never get a valid access group or never successfully reach a portal.

If your security team wants something stronger (e.g., no operator created at all until AD groups are fully evaluated), that’s essentially a product enhancement request. Today’s OIDC flow doesn’t expose array claims early enough to support that design purely by configuration.

Thanks for the response.

we managed to get the OIDC flow working. Below are the detailed steps I followed:

  1. Created an operator with the GuestAccessGroup

    • One important thing I noticed is that the operator’s access group should not be the same as the least-privilege access group that is typically assigned (for example, MyOrg:Unauthenticated) in the Browser Requestor type.
  2. Mapped the OIDC claims

    • The out-of-the-box data page D_pzSSOAttributes is not available during post-authentication.

    • The only way I found to access the claims was to map them in the Mapping tab of the Authentication Service and store them either on a temporary page or directly on the OperatorID page.

    • In my case, I created a new Value List property called ADRoles in operatorID class and mapped it to the {ad_roles} claim array.

  3. Applied the business logic in the post-authentication activity

    • During post-authentication, the ADRoles value list is available on OperatorID page.

    • I used it to determine the appropriate model operator based on the user’s AD roles.

  4. Updated the operator’s access group

    • Pega restricts several methods on the OperatorID page (such as Property-Set, Page-Copy, Java steps, Obj-Open-*, etc.) for security reasons.

    • To update the access group on the OperatorID page, you either need to disable the relevant DSS (see the Pega documentation for details) or use a different approach that safely updates the operator.

    • I chose not to disable the DSS. Instead, I created a temporary page (TempOperatorPage), populated it with the required operator information along with the details derived from the model operator, and once everything was validated, I performed a Page-Merge-Into from TempOperatorPage to OperatorID.

    • Finally, I saved the OperatorID, performed a commit, and executed the remaining out-of-the-box Java steps to set pyAuthenticationPolicyResult to true or false based on the authentication logic.