PingOne Platform APIs

Use a trusted issuer JWT for token enrichment

Token exchange in PingOne enables admins to map attributes from a source’s subject or actor JWTs to the PingOne generated token to improve interoperability with OIDC applications. PingOne accepts JWTs from first-party or third-party sources, where the third-party sources are identified in PingOne as trusted issuers. Claims from these JWTs can be mapped to the PingOne generated tokens using the following expression variables:

context.requestData.subjectToken
context.requestData.subjectTokenType
context.requestData.subjectTokenHeader
context.requestData.actorToken
context.requestData.actorTokenType
context.requestData.actorTokenHeader
context.requestData.requestedTokenType

context.requestData.subjectToken.[claimName]
context.requestData.subjectTokenHeader.[attributeName]
context.requestData.actorToken.[claimName]
context.requestData.actorTokenHeader.[attributeName]

Through a token exchange action, mapped claims from a trusted issuer’s JWT can be added to a PingOne-generated access token. The configuration and runtime actions required for token enrichment from a third-party trusted issuer include:

  1. Define the issuers that PingOne can trust. To learn more, refer to Trusted issuers.

  2. Assign trusted issuers to the applications that need to use third-party signed JWTs from the assigned trusted issuers as inputs in their token-exchange token requests. To learn more, refer to Application trusted issuer assignments.

  3. Define attribute mappings in the resource attributes service that are the intended audiences of the access tokens in response to the token-exchange token requests. To learn more, refer to Create resource attribute.

  4. At runtime, send the token-exchange requests in which the subject or the actor token (or both) are signed JWTs from the application’s assigned trusted issuers. To learn more, refer to Token (token_exchange).

Runtime flow processing details

Here are the high-level steps for processing a token exchange flow:

  1. The PingOne authorization server receives the token exchange request with a specified subject_token property (and optional actor_token property) configured.

  2. The PingOne authorization server validates the tokens:

    • The authorization server performs initial validation (introspection) of several claims such as (but not limited to) token expiration, JWT signature, and token revocation.

    • After that, validation is done through the admin-defined attribute mapping expressions.

  3. The PingOne authorization server issues a new token that matches one of the requested_token_type values configured in the token request (for example, requested_token_type=urn:ietf:params:oauth:token-type:access_token).

  4. The resource server or application receiving the issued token can perform additional validation, for example in a delegation use case where the act claim is present.

Configuration for token enrichment

Here is a sample use case in which the resulting token is enriched with information from an ID token issued by PingFederate, a trusted issuer. Before calling the authorization server in Task 1 below, you must complete the steps to (1) define the trusted issuer, (2) assign the trusted issuer to the application, and (3) define the custom resource mapping. In this case, you will map the securityLevel attribute from the trusted issuer’s actor token to the new Security claim in the resulting access token. The mapping expression in the custom resource, #root.context.requestData.actorToken.securityLevel, identifies the claim to add. After completing the configuration steps, you can initiate the authorization flow as follows:

Task 1. The application uses the authorization code flow to obtain an access token from PingOne with the following claims:

{
  "client_id": "34265a22-7330-4e5f-9aee-a93f892d873e",
  "iss": "https://auth.pingone.com/c6bcf635-3289-4b4a-ae02-5d4332363521/as",
  "jti": "222a2445-b540-44eb-b96e-e538f5285d3e",
  "iat": 1792621957,
  "exp": 1792625557,
  "aud": [
    "https://www.example.com/?here=apiAddress"
  ],
  "scope": "openid a.read.email",
  "sub": "bc782d02-9abc-45d9-9124-e8ec62d07fe8",
  "sid": "d8875daf-3e18-4f6d-bc73-3ce40a06d35a",
  "auth_time": 1792604632,
  "acr": "Single_Factor",
  "email": "jsmith@pingidentity.com",
  "env": "c6bcf635-3289-4b4a-ae02-5d4332363521",
  "org": "d4229c38-0f5e-4bf7-9292-9d3b0df7294c"
}

Task 2. The application also obtains an ID token from the trusted issuer, PingFederate, with the following claims:

{
  "sub": "jsmith",
  "aud": "ac_oic_client",
  "jti": "zHSThjqv7PAVk6QLLl7MYP",
  "iss": "https://ssoux1.js.ping-eng.com:9031",
  "iat": 1792621936,
  "exp": 1792625536,
  "sid": "Gv6upknTl9kEhMigRlbozknRlK8",
  "amr": [
    "oidcPolicy"
  ],
  "auth_time": 1792621932,
  "acr": "oidcPolicy",
  "securityLevel": "Class 3"
}

This ID token from PingFederate includes a custom claim named securityLevel with a value of Class 3. Using token exchange, you will add this claim to the access token.

Task 3. The application sends a token request using the token exchange grant type. In this sample, the application’s tokenEndpointAuthMethod value is CLIENT_SECRET_POST, which does not require an Authorization header, and the client_id and client_secret properties are sent in the request body. The token request also includes the subject_token, subject_token_type, actor_token, and actor_token_type properties to identify the JWTs from the trusted issuer:

curl --location --globoff '{{authPath}}/{{envID}}/as/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=' \
--data-urlencode 'client_id={{clientID}}' \
--data-urlencode 'client_secret={{clientSecret}}' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{subToken}}' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'actor_token={{actToken}}' \
--data-urlencode 'actor_token_type=urn:ietf:params:oauth:token-type:id_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'scope={{scopeID}}'

PingOne returns a token response with the following claims:

{
  "client_id": "34265a22-7330-4e5f-9aee-a93f892d873e",
  "iss": "https://auth.pingone.com/c6bcf635-3289-4b4a-ae02-5d4332363521/as",
  "jti": "af15d5cf-bb11-4934-9517-2742a8118163",
  "iat": 1792621982,
  "exp": 1792625582,
  "aud": [
    "https://www.example.com/?here=apiB"
  ],
  "scope": "write",
  "sub": "bc782d02-9abc-45d9-9124-e8ec62d07fe8",
  "sid": "d8875daf-3e18-4f6d-bc73-3ce40a06d35a",
  "auth_time": 1792604632,
  "acr": "Single_Factor",
  "bestie": "jsmith",
  "security": "Class 3",
  "env": "c6bcf635-3289-4b4a-ae02-5d4332363521",
  "org": "d4229c38-0f5e-4bf7-9292-9d3b0df7294c"
}

Based on the expression defined in the custom resource, the value from the securityLevel claim from the actor token is added as the security claim in the resulting access token.