---
title: Token exchange flows
description: /oauth2/access_token
component: pingam
version: 7.2
page_id: pingam:oauth2-guide:token-exchange-flows
canonical_url: https://docs.pingidentity.com/pingam/8.1/am-oauth2/token-exchange.html
llms_txt: https://docs.pingidentity.com/pingam/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
section_ids:
  token-exchange-example-prerequisites: Example prerequisites
  to-exchange-flows: Exchange tokens
---

# Token exchange flows

* Endpoints

  * [/oauth2/access_token](oauth2-access_token-endpoint.html)

Use the token exchange flows to exchange access and ID tokens for impersonated or delegated access and ID tokens, as explained in the [OAuth 2.0 Token Exchange](https://www.rfc-editor.org/info/rfc8693) specification. For implementation details and use case examples, see [OAuth 2.0 token exchange](oauth2-token-exchange.html).

## Example prerequisites

|   |                                                                                                                                                           |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Download the [ForgeRock grant flows collection](oauth2-grant-flow-collection.html) to configure AM for the examples, and to run the token exchange flows. |

The example procedure in this section assumes the following configuration:

* AM is configured as an OAuth 2.0/OpenID Connect provider in a realm called `mySubRealm`, and it is also configured for token exchange.

  > **Collapse: More information**
  >
  > * [Authorization server configuration](oauth2-configure-authz.html)
  >
  > * [Configure a new may act script](oauth2-token-exchange.html#may_act-script-token-exchange)
  >
  > * [Configure the OAuth 2.0 provider service for token exchange](oauth2-token-exchange.html#configure-provider-token-exchange)

* Two clients are registered in AM. One is the client that will create the subject token, and the other is the client that will request the token exchange.

  > **Collapse: Example client configuration**
  >
  > * `customerConfidentialClient`, the client that will make the requests for the subject tokens, has the following configuration:
  >
  >   * Client secret: `forgerock`
  >
  >   * Client type: `Confidential`
  >
  >   * Redirect URIs: `https://httpbin.org/anything`
  >
  >   * Scopes: `read write openid`
  >
  >   * Token Endpoint Authentication Method: `client_secret_post`
  >
  >     This is not required; confidential clients can authenticate in several ways, but `client_secret_post` makes the examples easier to read.
  >
  >   * Response Type: `code`
  >
  >     This is not required; depending on the flow you use to obtain the tokens, you will need a different response type configuration. This example assumes you will use the Authorization Code grant flow.
  >
  >     Configure the OAuth 2.0 provider accordingly to the response type.
  >
  > * `serviceConfidentialClient`, the client that will make the token exchange requests, has the following configuration:
  >
  >   * Client secret: `forgerock`
  >
  >   * Client type: `Confidential`
  >
  >   * Redirect URIs: `https://httpbin.org/anything`
  >
  >   * Response Type: `code`
  >
  >     This is not required; depending on the flow you use to obtain the tokens, you will need a different response type configuration. This example assumes you will use the authorization code grant flow.
  >
  >     Configure the OAuth 2.0 provider according to the response type.
  >
  >   * Scopes: `read write transfer`
  >
  >     The procedure will use the `transfer` scope to extend the scope of a token, which is why the client of the subject token does not have it configured.
  >
  >   * Token Endpoint Authentication Method:\`client_secret_post\`
  >
  >     This is not required; confidential clients can authenticate in several ways, but `client_secret_post` makes the examples easier to read.
  >
  >   * Token Exchange Auth Level: `10`

  > **Collapse: More information**
  >
  > * [Client registration](oauth2-register-client.html)
  >
  > * [OAuth 2.0 client authentication](oauth2-client-auth.html)
  >
  > * [Configure clients for token exchange](oauth2-token-exchange.html#configure-clients-token-exchange)
  >
  > * [OAuth 2.0 grant flows](oauth2-implementing-flows.html)
  >
  > * [OpenID Connect grant flows](../oidc1-guide/oidc-implementing-flows.html)

* The `ForgerockDemo` and `ForgerockDemo2` identities are registered in AM.

## Exchange tokens

The following procedure demonstrates how to exchange tokens for both impersonation and delegation cases:

1. Ensure you have configured AM as per the [example prerequisites](oauth2-token-exchange.html#token-exchange-example-prerequisites).

2. Obtain an access token and an ID token for the `ForgerockDemo/customerConfidentialClient` user and client combination.

   Use, for example, the [authorization code grant](../oidc1-guide/openid-connect-authorization-code-flow.html). Do not request the `transfer` scope; you will use it later in the procedure to expand the scopes of the exchanged token.

   [Introspect](varlist-oauth2-introspect-endpoint.html) the access token and [retrieve](../oidc1-guide/rest-api-oidc-userinfo-endpoint.html) the ID token information to check that they have the `may_act` claim.

   If they do not, review [Configuring AM for token exchange](token-exchange-configuration.html).

   > **Collapse: Example access token**
   >
   > ```
   > {
   >     "active": true,
   >     "scope": "read write",
   >     "realm": "/mySubRealm",
   >     "client_id": "customerConfidentialClient",
   >     "user_id": "ForgerockDemo",
   >     "username":"ForgerockDemo",
   >     "token_type": "Bearer",
   >     "exp": 1610552102,
   >     "sub": "(usr!ForgerockDemo)",
   >     "subname": "ForgerockDemo",
   >     "iss": "https://openam.example.com:8443/openam/oauth2/mySubRealm",
   >     "auth_level": 0,
   >     "authGrantId": "Zcw9MEANrbPjz4tuDLBfzmYrZP0",
   >     "may_act": {
   >         "client_id": "serviceConfidentialClient",
   >         "sub": "(usr!ForgerockDemo2)"
   >     },
   >     "auditTrackingId": "961c12ad-0a56-471e-9017-036f3cb873ce-571823"
   > }
   > ```
   >
   > Note the value of the `client_id` and `sub` claims.

   > **Collapse: Example ID token**
   >
   > ```
   > {
   >     "at_hash": "XAhNBCa7Utuc5dujUUA5mQ",
   >     "sub": "(usr!ForgerockDemo)",
   >     "auditTrackingId": "961c12ad-0a56-471e-9017-036f3cb873ce-576398",
   >     "iss": "https://openam.example.com:8443/openam/oauth2/mySubRealm",
   >     "tokenName": "id_token",
   >     "nonce": "123abc",
   >     "sid": "I0GdWDfy1qhahDl1PpEA0v5LDspul+qW70biBhetUCk=",
   >     "aud": "customerConfidentialClient",
   >     "c_hash": "0EEiPQxhwezCGa2bPgKYDQ",
   >     "acr": "0",
   >     "org.forgerock.openidconnect.ops": "Sj07ATq01pVwzF7Kqop0ZuCCxFg",
   >     "s_hash": "bKE9UspwyIPg8LsQHkJaiQ",
   >     "azp": "customerConfidentialClient",
   >     "auth_time": 1610549052,
   >     "realm": "/mySubRealm",
   >     "may_act": {
   >         "client_id": "serviceConfidentialClient",
   >         "sub": "(usr!ForgerockDemo2)"
   >     },
   >     "exp": 1610552698,
   >     "tokenType": "JWTToken",
   >     "iat": 1610549098
   > }
   > ```
   >
   > Note the value of the `aud` and `sub` claims.

   Impersonation and delegation examples will use one of these tokens as the subject token.

3. Obtain an access token and an ID token for the `ForgerockDemo2/customerConfidentialClient` user and client combination.

   Delegation examples will use one of these tokens as the actor token.

   Use the examples and the information from the previous step, if needed.

4. To perform a token exchange, the client makes an HTTP POST call to the authorization server's token endpoint.

   AM will issue a delegation token if the request includes an actor token, and an impersonation token otherwise.

   The request must contain, at least, the following parameters:

   * **grant_type**=`urn:ietf:params:oauth:grant-type:token-exchange`

   * **subject_token**=*token-to-be-exchanged*

     For this example, this is the access token obtained for the `ForgerockDemo/customerConfidentialClient` user and client combination.

   * **subject_token_type**=*type-of-subject-token*

     The value of the `subject_token_type` parameter is one of the following, depending of the token you are exchanging:

     > **Collapse: Supported subject token types**
     >
     > * `urn:ietf:params:oauth:token-type:access_token`
     >
     > * `urn:ietf:params:oauth:token-type:id_token`

   * **requested_token_type**=*type-of-request-token*

     The value of the `requested_token_type` parameter is one of the following, depending of the token you require:

     > **Collapse: Supported requested token types**
     >
     > * `urn:ietf:params:oauth:token-type:access_token`
     >
     > * `urn:ietf:params:oauth:token-type:id_token`

     For delegation use cases, include the actor token as follows:

   * **actor_token**=token_that_acts_on_behalf_of_the_subject

     For this example, this is the access token obtained for the `ForgerockDemo2/customerConfidentialClient` user and client combination.

   * **actor_token_type**=*type-of-actor-token*

     The value of the `actor_token_type` parameter is one of the following, depending of the token you are exchanging:

     > **Collapse: Supported actor token types**
     >
     > * `urn:ietf:params:oauth:token-type:access_token`
     >
     > * `urn:ietf:params:oauth:token-type:id_token`

     Confidential clients can authenticate to the `/oauth2/access_token` endpoint in several ways. This example uses the following parameters:

   * **client_id**=*client-requesting-impersonation*

   * **client_secret**=*client-secret*

   For more information, see [OAuth 2.0 client authentication](oauth2-client-auth.html) and [/oauth2/access_token](oauth2-access_token-endpoint.html).

   The client that makes the request must be the one authorized in the `may_act` claim of the subject token. In delegation cases, the subject of the actor token must also match the subject authorized in `may_act` claim of the subject token.

   The following is an example of a request for an *impersonation* token that exchanges an access token for another access token:

   ```bash
   $ curl \
   --request POST \
   --data "client_id=serviceConfidentialClient" \
   --data "client_secret=forgerock" \
   --data "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
   --data "scope=read write transfer" \
   --data "subject_token=HTVnsWhZhmyM13b-fMsbUqowBqQ" \
   --data "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
   --data "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
   "https://openam.example.com:8443/openam/oauth2/realms/root/realms/mySubRealm/access_token"
   ```

   The following is an example of a request for a *delegation* token that exchanges two access tokens for an ID token:

   ```bash
   $ curl \
   --request POST \
   --data "client_id=serviceConfidentialClient" \
   --data "client_secret=forgerock" \
   --data "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
   --data "scope=read write transfer" \
   --data "subject_token=HTVnsWhZhmyM13b-fMsbUqowBqQ" \
   --data "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
   --data "actor_token=f08f1fcf-3ecb-4120-820d-fb71e3f51c04" \
   --data "actor_token_type=urn:ietf:params:oauth:token-type:access_token" \
   --data "requested_token_type=urn:ietf:params:oauth:token-type:id_token" \
   "https://openam.example.com:8443/openam/oauth2/realms/root/realms/mySubRealm/access_token"
   ```

   Note that the `scopes` parameter has been added in both cases. Like in the regular OAuth 2.0/OpenID Connect flows, this parameter is not required, since it can be derived using the same mechanisms AM uses for regular OAuth 2.0/OpenID Connect flows (see [OAuth 2.0 scopes](oauth2-scopes.html)).

   For example purposes, the call requested all the scopes configured in the subject token plus the `transfer` scope, which is only available to this client. This is an example of expanding scopes.

   The `openid` scope is not needed to request ID tokens.

   |   |                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
   | - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   |   | There is no interaction with the user during token exchange, and therefore, it is impossible for AM to request consent about the expanded scopes/claims. It is the responsibility of the client application to ensure that either the user has consented beforehand, or that the expanded scope/claim is unrelated to the user's resources.Clients should not request more scopes/claims than those required to perform the task requested by the user. |

   To restrict the scopes/claims in the exchanged token, request fewer scopes/claims than those available to the subject token. For example, `--data "scopes=write"`.

   |   |                                                      |
   | - | ---------------------------------------------------- |
   |   | You can also use exchanged tokens as subject tokens. |

   > **Collapse: Example output for different token exchanges**
   >
   > * Access token to access token // ID token to access token :
   >
   >   ```json
   >   {
   >       "access_token": "sq2MACbRu6eEGGvoO4_rAEOnNOQ",
   >       "refresh_token": "wFAPJKb57e-4EGBduApXqFOvyDw",
   >       "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
   >       "scope": "read write",
   >       "token_type": "Bearer",
   >       "expires_in": 3599
   >   }
   >   ```
   >
   >   Refresh tokens are only issued if [AM is configured to issue them](oauth2-refresh-tokens.html).
   >
   > * Access token to ID token // ID token to ID token:
   >
   >   ```json
   >   {
   >       "access_token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJ3VTNpZkl…​",
   >       "refresh_token": null,
   >       "issued_token_type": "urn:ietf:params:oauth:token-type:id_token",
   >       "scope": "profile email",
   >       "token_type": "Bearer",
   >       "expires_in": 3599
   >   }
   >   ```
   >
   >   ID tokens are issued in the `access_token` object. Refresh tokens are never issued with ID tokens.

   > **Collapse: Error responses**
   >
   > * Invalid token exchange
   >
   >   ```
   >   {
   >       "error_description": "Invalid token exchange.",
   >       "error": "invalid_request"
   >   }
   >   ```
   >
   >   HTTP 400. The subject token, the actor token, or both, are invalid, of the wrong type, or cannot be exchanged due to the constrains imposed in the `may_act` claim.
   >
   > * Subject token type is required
   >
   >   ```
   >   {
   >       "error_description": "Subject token type is required.",
   >       "error": "invalid_request"
   >   }
   >   ```
   >
   >   HTTP 400. The subject token type has not been included in the request.

5. Introspect the token you obtained, and compare it with the subject token you exchanged to understand the differences.

   > **Collapse: Example of exchanged access token**
   >
   > Access token-specific claims are not populated unless the subject token contained that information. The only exception is the authentication level, as explained below:
   >
   > ```none
   > {
   >     "active": true,
   >     "scope": "read transfer write", (1)
   >     "realm": "/mySubRealm",
   >     "client_id": "serviceConfidentialClient", (2)
   >     "user_id": "ForgerockDemo",
   >     "username": "ForgerockDemo",
   >     "token_type": "Bearer",
   >     "exp": 1610551390,
   >     "sub": "(usr!ForgerockDemo)", (3)
   >     "subname": "ForgerockDemo",
   >     "iss": "https://openam.example.com:8443/openam/oauth2/mySubRealm",
   >     "auth_level": 10, (4)
   >     "authGrantId": "VokhnJCdMcvK-opywivldpKJyuk",
   >     "act": { (5)
   >         "sub": "(usr!ForgerockDemo2)"
   >     },
   >     "may_act": { (6)
   >         "client_id": "serviceConfidentialClient",
   >         "sub": "(usr!ForgerockDemo2)"
   >     },
   >     "auditTrackingId": "961c12ad-0a56-471e-9017-036f3cb873ce-565818"
   > }
   > ```
   >
   > |       |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
   > | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   > | **1** | The exchanged token has the scopes requested in the token exchange call. In this case, the exchanged token has the same scopes as the subject token, and the additional `transfer` token, which expands the subject token's authorization.                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
   > | **2** | The client ID has changed to that of the client that requested the exchanged token.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
   > | **3** | The subject of the exchanged token is the same as that of the subject token. The same is true for any other claims related to the subject, such as `username` and `subname`.The subject claim is in the format `(type!subject)`, where:- `subject` is the identifier of the user/identity, or the name of the OAuth 2.0/OpenID Connect client that is the subject of the token.
   >
   > - `type` can be one of the following:
   >
   >   * `age`. Specifies that the *subject* is an OAuth 2.0/OpenID Connect-related user-agent or client. For example, an OAuth 2.0 client, a Remote Consent Service agent, and a Web and Java Agent internal client.
   >
   >   * `usr`. Specifies that the *subject* is a user/identity. |
   > | **4** | When exchanging an access token for an access token, the authentication level is copied across. When exchanging an ID token for an access token, the authentication level is the value of the Token Exchange Auth Level field in the client profiles of the `serviceConfidentialClient` client.                                                                                                                                                                                                                                                                                                                                                                                                       |
   > | **5** | The `act` claim is only present in delegated tokens, and contains the subject of the actor token. When using delegated tokens as subject tokens, you may see the `act` claim nesting. For example:```none
   > "act": {
   >   "sub": "(usr!ForgerockDemo2)",
   >   "act": {
   >     "sub": "(usr!ForgerockDemo3)"
   >   }
   > }
   > ```                                                                                                                                                                                                                                                                                                                                                                                            |
   > | **6** | The `may_act` claim will only be present if the token meet the criteria established in the May Act script.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

   > **Collapse: Example of exchanged ID token**
   >
   > ID token-specific claims, such as `acr` and `nonce`, are not populated unless the subject token contained that information.
   >
   > ```none
   > {
   >    "sub": "(usr!ForgerockDemo)", (1)
   >    "auditTrackingId": "961c12ad-0a56-471e-9017-036f3cb873ce-607580",
   >    "iss": "https://openam.example.com:8443/openam/oauth2/mySubRealm",
   >    "tokenName": "id_token",
   >    "nonce": "123abc",
   >    "sid": "I0GdWDfy1qhahDl1PpEA0v5LDspul+qW70biBhetUCk=", (2)
   >    "aud": "serviceConfidentialClient",
   >    "acr": "0",
   >    "org.forgerock.openidconnect.ops": "eVzAOcG-UAyweUaGeEhggWDDWvU",
   >    "azp": "serviceConfidentialClient",
   >    "auth_time": 1610552780,
   >    "realm": "/mySubRealm",
   >    "may_act": { (3)
   >        "client_id": "serviceConfidentialClient",
   >        "sub": "(usr!ForgerockDemo2)"
   >    },
   >    "act": { (4)
   >        "sub": "(usr!ForgerockDemo2)"
   >    },
   >    "exp": 1610556942,
   >    "tokenType": "JWTToken",
   >    "iat": 1610553342
   > }
   > ```
   >
   > |       |                                                                                                                                                                                                                                                                                                            |
   > | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   > | **1** | The subject of the exchanged token is the same as that of the subject token.                                                                                                                                                                                                                               |
   > | **2** | The audience is the client ID that requested the exchanged token. Note that the authorized party (`azp`) is also this client ID.                                                                                                                                                                           |
   > | **3** | The `may_act` claim will only be present if the token meet the criteria established in the May Act script.                                                                                                                                                                                                 |
   > | **4** | The `act` claim is only present in delegated tokens, and contains the subject of the actor token. When using delegated tokens as subject tokens, you may see the `act` claim nesting. For example:```none
   > "act": {
   >   "sub": "(usr!ForgerockDemo2)",
   >   "act": {
   >     "sub": "(usr!ForgerockDemo3)"
   >   }
   > }
   > ``` |
