loading
Preparing LoginRadius developer resources
Mission: Help enterprises accelerate digital transformation with our fully-managed Customer IAM technology.
Skip to main content

Token Exchange

OAuth 2.0 Token Exchange (RFC 8693) defines a Security Token Service (STS) protocol that allows a client to obtain a new security token by presenting an existing token to the LoginRadius Token Endpoint. Unlike the Authorization Code Flow — where the input is a user's browser grant — here the input is an already-issued token and the entire flow is back-channel only.

LoginRadius supports two token exchange semantics, controlled by whether an actor_token is present in the request — see Impersonation vs. Delegation below.

Important: Any application may perform token exchange, provided the Token Exchange grant type is explicitly enabled on that application in the LoginRadius Admin Console.


Overview

The flow operates entirely server-side (back-channel) — there is no browser redirect phase:

  1. Token Exchange Phase — the requesting application presents an existing access token as the subject_token to the LoginRadius Token endpoint. LoginRadius — acting as the STS — validates it, applies RBAC and trust policy, then issues a new access token scoped to the target Resource API.

Flow Diagram


Impersonation vs. Delegation

RFC 8693 defines two exchange semantics that determine what identity information appears in the issued token. The presence or absence of the actor_token parameter controls which semantic applies.

AspectImpersonationDelegation
actor_token requiredNoYes
What the issued token representsThe subject's identity only — the acting client is not recordedBoth the subject's identity and the acting service's identity
act claim in issued JWTNot presentPresent — contains the actor's sub in clientId@client format
What the downstream service seesOnly the original userThe user (sub) and the service acting on their behalf (act.sub)
Audit trailSubject identity onlyFull chain: subject + acting service
Typical use caseExchanging a user token for a resource-scoped token (Use Cases 1, 2, 3)Multi-tier service chains where each hop must be attributable (Use Case 4)

Impersonation is the default behavior. When no actor_token is included, LoginRadius issues a token that carries the subject's identity as if the downstream service were talking directly to that user. The requesting client's identity is not embedded in the token.

Delegation applies when the requesting service needs to identify itself alongside the user. The actor_token (obtained via client_credentials) represents the acting service. LoginRadius embeds it in the issued token under the act claim, so downstream services can apply authorization decisions based on both who the user is and which service is acting for them. The act.sub value is derived from the sub claim of the actor token, which follows the format clientId@client.

Use Cases 1, 2, and 3 below demonstrate impersonation. Use Case 4 demonstrates delegation with the act claim — see Generating an Actor Token for how to obtain the actor_token it requires.


Configuration Guide

Complete these steps in the LoginRadius Admin Console before making any token exchange requests.

Step 1 — Create the User-Facing Application

This is the application that authenticates the end user and issues the initial access token.

  1. In the Admin Console, select Application from the left panel.
  2. Click Add App and provide a name.
  3. Save the application. Copy the ClientId and ClientSecret and store them securely — the ClientSecret is shown only once.
  4. Under Redirect & Logout URLs, whitelist your login redirect URI.
  5. Save the changes.

Step 2 — Create the Resource API

The Resource API defines the protected resource, its available scopes, token TTL, and which clients are permitted to present a subject token for exchange.

  1. In the Admin Console, go to Authorization → APIs.
  2. Click Add API.
  3. Set a human-readable API Name.
  4. Set the Identifier — this URI is used as the resource parameter in exchange requests and as the aud claim in issued tokens (e.g. https://orders.api).
  5. Add the Resource API Scopes and configure the Token TTL.
  6. Enable the Token Exchange toggle.
  7. Under Trusted Clients, select the user-facing application created in Step 1.
  8. Save the changes.

Note: If the application performing the token exchange is the same application that originally issued the subject_token, it is implicitly trusted and does not need to be added to Trusted Clients. This simplifies single-application setups where one client handles both user authentication and token exchange.

Step 3 — Configure the Requesting Application

Any application with the Token Exchange grant type enabled can perform token exchange.

  1. In the Admin Console, select Application from the left panel.
  2. Open the application that will perform the token exchange (create one if needed).
  3. Under Grant Types, select Token Exchange.
  4. Under Audience and Scopes, select the Resource API created in Step 2 and select the permitted scopes.
  5. Save the changes. Copy the ClientId and ClientSecret and store them securely — the ClientSecret is shown only once.

Step 4 — Assign Role & ResourcePermissions

The user's role must include ResourcePermissions entries that grant scopes for the target Resource API. These determine the maximum scopes the exchanged token can carry for that user.

  1. In the Admin Console, go to Authorization → Roles.
  2. Open or create the role assigned to the user.
  3. Under Resource Permissions, select the Resource API created in Step 2.
  4. Select the scopes this role is permitted to access.
  5. Save the changes.

Step-by-Step Walkthrough

Step 1 — Obtain the Subject Token

The subject_token is an existing access token that represents the identity on whose behalf the exchange is being performed. It must be valid and not expired.

For user-based flows, the subject token is obtained when the end user authenticates via the authorization_code, password, or device_code grant. For service-to-service flows with no user context, it is obtained via the client_credentials grant, where the subject is the service client itself.


Step 2 — Send the Token Exchange Request

The requesting application sends the subject_token to the token endpoint, specifying the target Resource API via the resource parameter.

Endpoint

POST https://{SiteURL}/api/oidc/{OIDCAppName}/token

Replace {SiteURL} with either <TenantName>.hub.loginradius.com or your custom domain, and {OIDCAppName} with your configured OIDC App name in the LoginRadius Admin Console.

Example Request

curl --location 'https://{SiteURL}/api/oidc/{OIDCAppName}/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<APP_CLIENT_ID>' \
--data-urlencode 'client_secret=<APP_CLIENT_SECRET>' \
--data-urlencode 'subject_token=<USER_INITIAL_ACCESS_TOKEN>' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'resource=<RESOURCE_API_IDENTIFIER>' \
--data-urlencode 'scope=read write' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange'

Never include client_secret in client-side code. Token exchange must always be performed from a secure back-end service.


Step 3 — Process the Request

LoginRadius authenticates the client, validates the subject token's signature and expiry, confirms the target Resource API has Token Exchange enabled, and checks that the subject token's original client is listed in Trusted Clients. It then intersects the requested scopes against the user's role grants and issues the token.


Step 4 — Receive and Use the Issued Token

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}

The issued token is strictly scoped to the target Resource API. It must be used as the Authorization: Bearer <token> header when calling that API. It is not valid for LoginRadius Auth APIs or any other service.


Use Cases

Use Case 1 — Service-to-Service Delegation (Impersonation)

A web application needs to fetch a user's order history from the Orders API. The user's current access token is scoped to the web app's own audience and cannot be used directly against the Orders API, which requires its own audience-specific token. No actor_token is provided — the issued token impersonates the user, and the Orders API sees only the user's identity.

Prerequisites:

  • Resource API https://orders.api configured with Token Exchange enabled
  • Web App client listed in Orders API Trusted Clients
  • Web App has the Token Exchange grant type enabled
  • User has a role with read scope granted for Orders API via ResourcePermissions

Request:

POST /api/oidc/{OIDCAppName}/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://orders.api
&scope=read
&client_id=webapp-client
&client_secret=<secret>

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read"
}

Use Case 2 — Scope Downscoping (Impersonation)

A Payment Processing Service holds a broad token (read write refund admin) and needs to call a Notification Service that only requires the notify scope. Downscoping limits the blast radius if the Notification Service is compromised. No actor_token is used — the Notification Service sees only the user's identity.

Scope Calculation:

RequestedRole Grants (ResourcePermissions)Final Token Scopes
notifynotify readnotify

Security Benefit: Even if the Notification Service is compromised, the token cannot be used for read operations.


Use Case 3 — Role-Based Access Enforcement (Impersonation)

Token Exchange enforces RBAC — different users receive different scopes for the same Resource API based on their assigned roles. No actor_token is used; the Healthcare API sees only the individual user's identity and grants access accordingly.

Scope Calculation Examples:

User RoleRequested ScopesRole Grants (ResourcePermissions)Final Token ScopesNotes
doctorread writeread writeread writeFull intersection
nurseread writereadreadReduced to role permissions
adminread write admin deleteread write admin deleteread write admin deleteAll granted
nurse(omitted)readreadDefault to all role-permitted scopes

Use Case 4 — Multi-Tier Delegation Chain (Delegation)

A three-tier architecture where Frontend → API Gateway → Data Service. Each hop performs a token exchange and the act claim tracks the delegation chain for audit purposes.

When an actor_token is included, LoginRadius issues a delegated token containing the act claim to identify the acting service. The actor_token must be obtained via the client_credentials grant. Downstream resource servers can read the act claim to distinguish who is acting on whose behalf and apply fine-grained authorization accordingly.

Token 1 — issued after Exchange 1 (no actor). The gateway receives a token scoped to https://gateway.api with the user as the subject.

{
"sub": "user123",
"aud": "https://gateway.api",
"client_id": "gateway-client",
"scope": "gateway.access"
}

Token 2 — issued after Exchange 2 (with actor). The Data Service receives a token that carries both the user's identity and the gateway's identity via the act claim.

{
"sub": "user123",
"aud": "https://data.api",
"client_id": "gateway-client",
"scope": "data.read",
"act": {
"sub": "gatewayClientId@client"
}
}

The sub claim always carries the original user's identity. The act.sub value is the sub from the actor token, formatted as gatewayClientId@client, identifying the gateway as the acting service.


Generating an Actor Token

The actor_token used in Use Case 4 represents the identity of the service performing the token exchange on behalf of the subject. It must always be an OAuth 2.0 access token obtained via the client_credentials grant — it represents the calling service's own identity, not a user's. The application used to obtain it must have the client_credentials grant type enabled and must be authorized for the target Resource API and its scopes, as configured in Configuration Guide — Step 2.

Endpoint

POST https://{SiteURL}/api/oidc/{OIDCAppName}/token

Example Request

curl --location 'https://{SiteURL}/api/oidc/{OIDCAppName}/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<SERVICE_CLIENT_ID>' \
--data-urlencode 'client_secret=<SERVICE_CLIENT_SECRET>' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'audience=<RESOURCE_API_IDENTIFIER>' \
--data-urlencode 'scope=<RESOURCE_API_SCOPES>'

Response

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}

The returned access_token is the actor_token. Pass it alongside the subject_token in the token exchange request, and set actor_token_type to urn:ietf:params:oauth:token-type:access_token.

The sub claim in the actor token follows the format clientId@client (e.g. gatewayClientId@client). This value is what gets embedded in the act.sub claim of the issued token, identifying the acting service to downstream resource servers.


Token Request Parameters

Required Parameters

ParameterTypeDescription
grant_typestringMust be urn:ietf:params:oauth:grant-type:token-exchange
client_idstringThe application's client identifier
client_secretstringThe application's client secret
subject_tokenstringThe existing access token representing the subject's identity. Must be valid and not expired.
subject_token_typestringMust be urn:ietf:params:oauth:token-type:access_token — the only supported value
resourcestringAbsolute URI identifier of the target Resource API (must match the Identifier field of a registered Resource API). Must not include a fragment component. Can be repeated for multiple resources.

Optional Parameters

ParameterTypeDescription
scopestringSpace-delimited list of requested scopes. Must be a subset of scopes defined in the target Resource API. If omitted, all Resource API scopes are used as the default request, subject to role grants.
actor_tokenstringAccess token representing the service authorized to act on behalf of the subject. Used for delegation chains. Must be obtained via the client_credentials grant — see Generating an Actor Token.
actor_token_typestringRequired when actor_token is present. Must not be present when actor_token is absent. Must be urn:ietf:params:oauth:token-type:access_token.
requested_token_typestringDefaults to urn:ietf:params:oauth:token-type:access_token.

Token Response Fields

FieldTypeDescription
access_tokenstringThe newly issued JWT access token
issued_token_typestringAlways urn:ietf:params:oauth:token-type:access_token
token_typestringAlways Bearer
expires_inintegerToken lifetime in seconds
scopestringSpace-delimited granted scopes

Token Behavior

Issued Token Structure

The access token issued by the Token Exchange endpoint is a JWT signed with RS256, carrying the subject's identity, the target audience, the granted scopes, and — when delegation is used — the actor's identity in the act claim.

ClaimDescription
issAuthorization server identifier
subSubject identity carried over from the original subject_token
audTarget Resource API Identifier
expExpiration time — determined by TTL rules below
iatIssued-at timestamp
jtiUnique token identifier
scopeSpace-delimited string of the final calculated scopes
client_idAuthenticated client identifier
actActor identity claim — present only when actor_token is provided (delegation). Absent during impersonation.

TTL Rules

The TTL of the issued access token is the minimum of two values:

Generated Token TTL = min(
Subject Token Remaining TTL,
Resource API Configured TokenTTL
)

For example, if the subject token has 45 minutes remaining and the Resource API is configured with a 15-minute TTL, the issued token will expire in 15 minutes.

This ensures the exchanged token never outlives the original subject context and resource-specific lifetime constraints are preserved.

Token Chain Expiry Propagation — All tokens generated through a Token Exchange chain are logically bound to the initial subject token. When the root subject token expires or is invalidated (e.g. on logout), all downstream tokens derived from it are also treated as expired, regardless of their individual TTL values.

User logs in → authorization_code → access_token (T1)
T1 → token_exchange → access_token (T2)
T2 → token_exchange → access_token (T3)

User logs out or T1 expires
→ T2 and T3 are also treated as expired

Scope Determination Rules

A scope only appears in the issued token if it satisfies all four constraints simultaneously. If any constraint excludes it, the scope is dropped — and if the resulting set is empty, the request fails with invalid_scope.

FinalScopes =
RequestedScopes
∩ SubjectEligibleScopes (when subject token audience matches the target Resource API)
∩ ActorEligibleScopes (when actor_token is provided)
∩ ResourceAllowedScopes

If the subject token's audience differs from the target Resource API, the subject token's own scopes are not carried over directly. Eligible scopes are derived from what the target Resource API permits for Token Exchange, intersected with the requested scopes and the user's role grants.


Error Handling

All errors are returned as a JSON body with an HTTP 400 status code.

Error Response Format

{
"error": "ERROR_CODE",
"error_description": "Human-readable description of the error"
}

Security Considerations

AreaGuidance
Token BindingValidate subject token signature, exp, nbf, revocation status, and iss before exchange. Validate actor tokens with the same rigor.
Replay PreventionValidate the jti claim against a cache of recently used tokens to prevent token replay.
Strict DownscopingExchanged tokens must never carry broader privileges than the original subject token. Final scopes are always the intersection of all applicable constraints — upscoping is not permitted.
Token LifetimeIssue tokens with short lifetimes (1 hour or less). Token Exchange only issues access tokens.
Token Chain ExpiryAll tokens in a delegation chain are logically bound to the root subject token. Invalidation of the root (logout, revocation) must propagate to all derived tokens.
Secret HandlingThe client_secret must never be embedded in client-side code or version control. Token exchange must always be performed from a secure back-end service. Rotate secrets immediately if exposure is suspected.