Documentation
Documentation
Step 2: Token Exchange
Exchange the ID Token for an ID-JAG (Identity Assertion Authorization Grant) using RFC 8693 Token Exchange.
Steps 1, 2, and 3 are identical whether you're calling a REST API or an MCP server. The only difference is Step 4: the transport. Change the resource parameter to point at your MCP server URL instead of a REST API URL.
What is Token Exchange?
Token Exchange (RFC 8693) allows you to exchange one security token for another. In XAA, we exchange an ID Token (which proves user identity to your application) for an ID-JAG (which asserts user identity to a third-party authorization server).
Request parameters
| Parameter | Required | Value |
|---|---|---|
grant_type | Yes | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Yes | The ID Token from Step 1 |
subject_token_type | Yes | urn:ietf:params:oauth:token-type:id_token |
requested_token_type | Yes | urn:ietf:params:oauth:token-type:id-jag |
audience | Yes | The Authorization Server URL. Becomes the aud claim in the ID-JAG. |
resource | Yes | The Resource Server API URL, embedded as the resource claim. |
scope | Optional | Requested scopes (e.g., todos.read) |
audience and resource serve different roles: audience identifies who will validate the ID-JAG (the Authorization Server), while resource identifies what will be accessed (your API). Both are required.
Implementation
Developer-registered clients use client_secret_post: credentials go in the POST body, not in an Authorization: Basic header.
Response
A successful response:
| Field | Description |
|---|---|
access_token | The ID-JAG token |
issued_token_type | Confirms this is an ID-JAG |
token_type | N_A since ID-JAG is not used as a bearer token directly |
expires_in | Token lifetime in seconds (typically 5 minutes) |
scope | Granted scopes |
The ID-JAG contains claims that identify the user and authorize access:
iss- Issuer (the IDP that issued the original ID Token)sub- Subject (user identifier from the ID Token)aud- Audience (the authorization server URL)client_id- The resource client ID ({client_id}-at-{resource_id}), the identity your app presents to the Authorization Server in Step 3. This is distinct from the IDP client ID used in Steps 1 and 2.resource- The canonical Resource Server URLscope- Requested permissionsjti- Unique token identifier (prevents replay attacks)exp- Expiration time (short-lived, typically 5 minutes)iat- Issued at time
Decoded ID-JAG example
client_id is the resource client ID, the -at-{resource} form created when you registered. Your app authenticates with this identity in Step 3.
Error handling
| Error | Cause | Fix |
|---|---|---|
invalid_grant | ID Token is expired, invalid, or has been revoked | Re-authenticate in Step 1 to get a fresh ID Token |
unauthorized_client | Client is not authorized for token exchange | Verify your client was registered with the correct grant types |
invalid_target | Resource URL is not registered or allowed | Re-register the resource using the wizard; the audience + resource must match a resource connection |
invalid_scope | Requested scope is not permitted for your client | Remove the invalid scopes, or re-register and add them in Wizard Step 3 |
Security considerations
- Short Expiry: ID-JAGs have short lifetimes (5 minutes) to minimize risk
- Single Use: ID-JAGs contain a
jticlaim and may be single-use - Audience Binding: ID-JAGs are bound to a specific authorization server
- Client Authentication: Always use client credentials when exchanging tokens
Next step
Now that you have an ID-JAG, proceed to Step 3: JWT Bearer Grant to exchange it for an access token.
On this page