Overview
Token Exchange lets a client swap an existing access token for a new one scoped to a different service. A travel-planning Agent, for example, can exchange the employee's token for one that grants read-only access to an HR MCP service — without prompting the employee to log in again.
The feature implements RFC 8693 (OAuth 2.0 Token Exchange) and is available on every IDaaS instance with Agent Identity Security enabled.
For background on the protocol and supported grant flows, see What is Token Exchange.
Before you begin
Token Exchange is on by default for Agent applications. No extra configuration is needed.
Limitations
Item | Constraint |
Token issuer | Only tokens issued by the same IDaaS authorization server can be exchanged. |
Client authorization | The calling client must already be authorized for the target audience and scope. |
Token validity | The subject token must not be expired and must pass signature verification. |
Chain depth | The server enforces a maximum exchange depth to prevent infinite delegation loops. |
Supported token types | Only access-token-to-access-token exchanges are supported. |
Exchange a token
Step 1: Obtain an initial access token
Get a valid access token for the calling client through the OAuth 2.0 authorization code flow. This token becomes the subject_token in the exchange request.
If you already hold a valid access token (for example, from a user login session), skip to Step 2.
Step 2: Call the token endpoint
Send a POST request to the IDaaS token endpoint.
Endpoint
POST https://{domain}/api/v2/iauths_system/oauth2/tokenReplace {domain} with your IDaaS instance domain (for example, example.Chinese IDaaS domain).
Parameters
Parameter | Type | Required | Example | Description |
grant_type | String | Yes | urn:ietf:params:oauth:grant-type:token-exchange | Must be |
subject_token | String | Yes | <subject_token> | The access token that represents the end-user identity. |
subject_token_type | String | Yes | urn:ietf:params:oauth:token-type:access_token | Must be |
requested_token_type | String | No | urn:ietf:params:oauth:token-type:access_token | The type of token to issue. Defaults to |
scope | String | Yes | mcp-server|user:read | Permissions for the new token. Format: |
audience | String | No | mcp-server | Logical name of the target service. Must match the audience portion of |
client_id | String | Yes | <client_id> | The OAuth client ID of the calling application. |
client_secret | String | Yes | <client_secret> | The OAuth client secret of the calling application. |
Example request
curl -X POST 'https://{domain}/api/v2/iauths_system/oauth2/token' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token=<subject_token>' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'scope=mcp-server|user:read' \
--data-urlencode 'audience=mcp-server' \
--data-urlencode 'client_id=<client_id>' \
--data-urlencode 'client_secret=<client_secret>'Sample response
{
"access_token": "eyJraWQiOiJBVVRI...xxxx",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"expires_at": 1772605468,
"scope": "user:read"
}Response fields
Field | Type | Description |
access_token | String | The newly issued access token. |
issued_token_type | String | The token type URN of the issued token. |
token_type | String | Always |
expires_in | Integer | Lifetime of the token in seconds. |
expires_at | Integer | Unix timestamp when the token expires. |
scope | String | The granted permission scope. |
Error codes
HTTP status | Error | Cause | Fix |
400 | invalid_grant | Wrong | Set |
400 | invalid_request | A required parameter is missing, malformed, or the subject token is invalid. | Check that every required parameter is present and the subject token has not expired. |
400 | invalid_target | The audience or scope is not recognized. | Confirm the |
401 | unauthorized_client | Client authentication failed. | Verify |
Step 3: Call the target service
Pass the new token in the Authorization header when calling the target resource server:
curl -X GET 'https://<resource_server>/api/protected-resource' \
-H 'Authorization: Bearer <access_token>'Replace the placeholders with your values:
<resource_server>— domain of the target service.<access_token>— the token returned in Step 2.
Example: Agent-to-MCP delegation
Use case
A company runs a travel-planning Agent and an HR MCP service. When an employee asks the Agent to plan a trip, the Agent needs the employee's job level and travel budget from the HR service.
With Token Exchange, the Agent swaps the employee's token for one scoped to the HR MCP service. The employee isn't prompted to log in again, and HR sees exactly who is making the request.
How the roles map
RFC 8693 role | In this case |
Front service | Travel system (web app) |
Resource server A | Travel-planning Agent |
Resource server B | HR MCP service |
Authorization server | IDaaS |
What you get
Least-privilege access — the exchanged token only grants the permissions the Agent needs on the HR service.
No extra login — the original user identity carries through the chain.
Full audit trail — every hop is recorded and traceable (see below).
Audit and troubleshooting
View exchange logs in the console
Every token exchange request — successful or not — is logged in the IDaaS console under Log > Call Logs with the event type Token Exchange. Each entry records:
The client that requested the exchange.
The subject (end-user account).
The subject token ID and related metadata.
Trace the delegation chain in a JWT
Every token produced by IDaaS Token Exchange is a signed JWT. The custom claim _idaas_imp embeds the full delegation chain, so you can decode any token to see exactly which clients participated.
Decoded payload (three-hop chain)
{
"sub": "user_xxxxxxxxxxxxxxxxxxxx",
"scope": "user:read",
"jti": "AT_03",
"iss": "https://{domain}/api/v2/iauths_system/oauth2",
"iat": 1772604268,
"nbf": 1772604268,
"exp": 1772605468,
"aud": "mcp-server",
"client_id": "app_03",
"_idaas_iid": "idaas_xxxxxxxxxxxxxxxxxxxx",
"_idaas_tag": "user-auth",
"_idaas_imp": {
"jti": "AT_02",
"client_id": "app_02",
"_idaas_imp": {
"jti": "AT_01",
"client_id": "app_01"
}
}
}Reading the chain
Fields | Meaning |
| The current token — issued to |
| The subject token used in this exchange — belonged to |
| The original token — issued to |