Token exchange flows
- Endpoints
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 specification. For implementation details and use case examples, see OAuth 2.0 token exchange.
Example prerequisites
Download the ForgeRock grant flows collection 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. -
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.
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
-
-
-
The
ForgerockDemo
andForgerockDemo2
identities are registered in AM.
Exchange tokens
The following procedure demonstrates how to exchange tokens for both impersonation and delegation cases:
-
Ensure you have configured AM as per the example prerequisites.
-
Obtain an access token and an ID token for the
ForgerockDemo/customerConfidentialClient
user and client combination.Use, for example, the authorization code grant. Do not request the
transfer
scope; you will use it later in the procedure to expand the scopes of the exchanged token.Introspect the access token and retrieve the ID token information to check that they have the
may_act
claim.If they do not, review Configuring AM for token exchange.
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
andsub
claims.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
andsub
claims.Impersonation and delegation examples will use one of these tokens as the subject token.
-
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.
-
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: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: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: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 and /oauth2/access_token.
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 inmay_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:
$ 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:
$ 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).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. Example output for different token exchanges
-
Access token to access token // ID token to access token :
{ "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.
-
Access token to ID token // ID token to ID token:
{ "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.
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.
-
-
Introspect the token you obtained, and compare it with the subject token you exchanged to understand the differences.
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:
{ "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
andsubname
.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 theact
claim nesting. For example:"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.Example of exchanged ID token
ID token-specific claims, such as
acr
andnonce
, are not populated unless the subject token contained that information.{ "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 theact
claim nesting. For example:"act": { "sub": "(usr!ForgerockDemo2)", "act": { "sub": "(usr!ForgerockDemo3)" } }
-