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.
Tip
Download the ForgeRock OAuth 2.0 and OpenID Connect Postman 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.
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 accordingly 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.
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 thetransfer
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 the "Configuring AM for Token Exchange" section.
{ "active": true, "scope": "read write", "realm": "/mySubRealm", "client_id": "customerConfidentialClient", "user_id": "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.{ "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: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: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: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 About 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.Caution
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"
.Tip
You can also use exchanged tokens as subject tokens.
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.
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.
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", "realm": "/mySubRealm", "client_id": "serviceConfidentialClient", "user_id": "ForgerockDemo", "token_type": "Bearer", "exp": 1610551390, "sub": "(usr!ForgerockDemo)", "subname": "ForgerockDemo", "iss": "https://openam.example.com:8443/openam/oauth2/mySubRealm", "auth_level": 10, "authGrantId": "VokhnJCdMcvK-opywivldpKJyuk", "act": { "sub": "(usr!ForgerockDemo2)" }, "may_act": { "client_id": "serviceConfidentialClient", "sub": "(usr!ForgerockDemo2)" }, "auditTrackingId": "961c12ad-0a56-471e-9017-036f3cb873ce-565818" }
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.The client ID has changed to that of the client that requested the exchanged token.
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
user_id
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.
For example,
(usr!demo)
, or(age!myOAuth2Client)
.The value of the
subname
claim matches the value of the subject portion of thesub
claim.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 the
serviceConfidentialClient
client.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:"act": { "sub": "(usr!ForgerockDemo2)", "act": { "sub": "(usr!ForgerockDemo3)" } }
The
may_act
claim will only be present if the token meet the criteria established in the May Act script.ID token-specific claims, such as
acr
andnonce
, are not populated unless the subject token contained that information.{ "sub": "(usr!ForgerockDemo)", "auditTrackingId": "961c12ad-0a56-471e-9017-036f3cb873ce-607580", "iss": "https://openam.example.com:8443/openam/oauth2/mySubRealm", "tokenName": "id_token", "nonce": "123abc", "sid": "I0GdWDfy1qhahDl1PpEA0v5LDspul+qW70biBhetUCk=", "aud": "serviceConfidentialClient", "acr": "0", "org.forgerock.openidconnect.ops": "eVzAOcG-UAyweUaGeEhggWDDWvU", "azp": "serviceConfidentialClient", "auth_time": 1610552780, "realm": "/mySubRealm", "may_act": { "client_id": "serviceConfidentialClient", "sub": "(usr!ForgerockDemo2)" }, "act": { "sub": "(usr!ForgerockDemo2)" }, "exp": 1610556942, "tokenType": "JWTToken", "iat": 1610553342 }
The subject of the exchanged token is the same as that of the subject token.
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.
For example,
(usr!demo)
, or(age!myOAuth2Client)
.The
subname
claim is not included in theOIDC Claims Script
. Therefore, AM does not add it to ID tokens by default.The audience is the client ID that requested the exchanged token. Note that the authorized party (
azp
) is also this client ID.The
may_act
claim will only be present if the token meet the criteria established in the May Act script.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:"act": { "sub": "(usr!ForgerockDemo2)", "act": { "sub": "(usr!ForgerockDemo3)" } }