Home

Documentation

Documentation

Step 2: Token Exchange

Exchange the ID Token for an ID-JAG (Identity Assertion Authorization Grant) using RFC 8693 Token Exchange.

Same flow for REST and MCP

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 a subject token — an ID Token or Refresh Token from Step 1 — for an ID-JAG (which asserts user identity to a third-party authorization server).

Two subject token options
  • Refresh Token (recommended): needs offline_access scope in Step 1, but lets you mint new ID-JAGs later without repeating it
  • ID Token: always available, but only good for one exchange right after login

Request parameters

ParameterRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenYesThe ID Token, or Refresh Token, from Step 1
subject_token_typeYesurn:ietf:params:oauth:token-type:id_token or urn:ietf:params:oauth:token-type:refresh_token
requested_token_typeYesurn:ietf:params:oauth:token-type:id-jag
audienceYesThe Authorization Server URL. Becomes the aud claim in the ID-JAG.
resourceYesThe Resource Server API URL, embedded as the resource claim.
scopeOptionalRequested scopes (e.g., todos.read)
audience vs resource

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

client_secret_post authentication

Developer-registered clients use client_secret_post: credentials go in the POST body, not in an Authorization: Basic header.

Add offline_access to the scope in Step 1 to get a refresh_token alongside the id_token — the same mechanism Okta and Microsoft Entra ID use. Swap subject_token and subject_token_type; the rest is unchanged:

Not single-use

Unlike the ID-JAG, the Refresh Token isn't consumed by this exchange — reuse it to mint more ID-JAGs until it expires or is revoked (RFC 8693 §2.1).

Response

A successful response:

JSON
FieldDescription
access_tokenThe ID-JAG token
issued_token_typeConfirms this is an ID-JAG
token_typeN_A since ID-JAG is not used as a bearer token directly
expires_inToken lifetime in seconds (typically 5 minutes)
scopeGranted scopes
ID-JAG Claims

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 URL
  • scope - Requested permissions
  • jti - Unique token identifier (prevents replay attacks)
  • exp - Expiration time (short-lived, typically 5 minutes)
  • iat - Issued at time

Decoded ID-JAG example

JSON

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

ErrorCauseFix
invalid_grantSubject token is expired, invalid, or has been revokedRe-authenticate in Step 1 to get a fresh subject token
unauthorized_clientClient is not authorized for token exchangeVerify your client was registered with the correct grant types
invalid_targetResource URL is not registered or allowedRe-register the resource using the wizard; the audience + resource must match a resource connection
invalid_scopeRequested scope is not permitted for your clientRemove the invalid scopes, or re-register and add them in Wizard Step 3

Security considerations

  1. Short Expiry: ID-JAGs have short lifetimes (5 minutes) to minimize risk
  2. Single Use: ID-JAGs contain a jti claim and may be single-use
  3. Audience Binding: ID-JAGs are bound to a specific authorization server
  4. Client Authentication: Always use client credentials when exchanging tokens
  5. Refresh Token storage: Treat it like any long-lived credential — never expose it in URLs or client-side code

Next step

Now that you have an ID-JAG, proceed to Step 3: JWT Bearer Grant to exchange it for an access token.

These materials and any recommendations within are not legal, privacy, security, compliance, or business advice. These materials are intended for general informational purposes only and may not reflect the most current security, privacy, and legal developments nor all relevant issues. You are responsible for obtaining legal, security, privacy, compliance, or business advice from your own lawyer or other professional advisor and should not rely on the recommendations herein.

Presented byOkta Developer

Copyright © 2026 Okta. All rights reserved.