All Products
Search
Document Center

Identity as a Service:Use Token Exchange to delegate access across services

Last Updated:Apr 08, 2026

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

Configure Agent ID Guard

Note

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/token

Replace {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 urn:ietf:params:oauth:grant-type:token-exchange.

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 urn:ietf:params:oauth:token-type:access_token.

requested_token_type

String

No

urn:ietf:params:oauth:token-type:access_token

The type of token to issue. Defaults to urn:ietf:params:oauth:token-type:access_token.

scope

String

Yes

mcp-server|user:read

Permissions for the new token. Format: <audience>|<scope>.

audience

String

No

mcp-server

Logical name of the target service. Must match the audience portion of scope.

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 Bearer.

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 grant_type value.

Set grant_type to the exact URN shown above.

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 audience value matches a registered resource and the scope follows the <audience>|<scope> format.

401

unauthorized_client

Client authentication failed.

Verify client_id and client_secret.

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

jti: AT_03, client_id: app_03

The current token — issued to app_03 in the latest exchange.

_idaas_imp.jti: AT_02, client_id: app_02

The subject token used in this exchange — belonged to app_02.

_idaas_imp._idaas_imp.jti: AT_01, client_id: app_01

The original token — issued to app_01 via a direct user login.