Token Exchange with External Identity Providers
ThunderID can exchange a token issued by an external identity provider (IdP) for a ThunderID-issued access token. This lets a client that authenticates users via an external OIDC provider, such as Asgardeo, Google, Azure AD, or Keycloak, obtain a ThunderID-scoped access token without requiring users to sign in again.
This uses RFC 8693 Token Exchange. For general information about the token exchange grant type, see Token Exchange.
When to Use
Use this when:
- A client authenticates users via an external OIDC provider and needs a ThunderID-scoped access token to call ThunderID-protected APIs.
- You want to centralize token issuance through ThunderID while delegating user authentication to an upstream IdP.
This is different from the trusted issuer feature. Trusted issuer lets ThunderID accept and validate tokens from a central authorization server directly, without issuing new tokens. Token exchange accepts an external token and issues a new ThunderID token in return.
Prerequisites
- ThunderID is running. See Get Started.
- An external OIDC identity provider that issues JWTs with an
issclaim and exposes a JWKS endpoint. - An access token with the
systemscope for the setup API calls.
Step 1: Register the External Identity Provider
Register the external IdP in ThunderID as an OIDC connection and enable it for token exchange. An OIDC connection is a single typed resource, so the redirect-flow fields (clientId, clientSecret, redirectUri, authorizationEndpoint, tokenEndpoint) are required even when the IdP is used for token exchange only. Three additional fields enable token exchange:
| Field | Required | Description |
|---|---|---|
issuer | Yes | The exact iss claim value in tokens from this IdP. ThunderID matches incoming tokens against this value to identify which IdP issued them. |
jwksEndpoint | Yes | The IdP's JWKS URL. ThunderID fetches public signing keys from this URL to verify token signatures. |
tokenExchangeEnabled | Yes | Set to true to allow this IdP's tokens to be exchanged. |
curl -X POST https://thunderid.example.com/connections/oidc \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Asgardeo",
"clientId": "my-external-client-id",
"clientSecret": "my-external-client-secret",
"redirectUri": "https://thunderid.example.com/gate/callback",
"authorizationEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/authorize",
"tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token",
"issuer": "https://api.asgardeo.io/t/myorg/oauth2/token",
"jwksEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/jwks",
"tokenExchangeEnabled": true
}'
Step 2: Create an OAuth Application with the Token Exchange Grant
Create an OAuth application and include urn:ietf:params:oauth:grant-type:token-exchange in its allowed grant types. This is the client that will perform exchanges on behalf of users.
curl -X POST https://thunderid.example.com/applications \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Token Exchange Client",
"inboundAuthConfig": [{
"type": "oauth2",
"config": {
"clientId": "te_client",
"clientSecret": "te_secret",
"grantTypes": ["urn:ietf:params:oauth:grant-type:token-exchange"],
"tokenEndpointAuthMethod": "client_secret_basic"
}
}]
}'
Step 3: Exchange the External Token
Send the external IdP token to ThunderID's token endpoint using the token exchange grant type:
curl -X POST https://thunderid.example.com/oauth2/token \
-u 'te_client:te_secret' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token=<JWT_FROM_EXTERNAL_IDP>' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:jwt' \
-d 'resource=https://api.example.com/app'
A successful response returns a ThunderID-issued access token:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}
The issued token's sub claim contains the external user's subject identifier from the original token. For permission-bearing requests, its aud claim is the explicit resource server identifier or the configured defaultResourceServer. An OIDC-only or scopeless request without resource uses the application's default audience (token.accessToken.defaultAudience), or the client_id when it is unset.
How ThunderID Validates the External Token
When processing an external token for exchange, ThunderID:
- Extracts the
issclaim and matches it against registered IdPs withtokenExchangeEnabled=true. - Fetches the matching IdP's JWKS and verifies the token signature.
- Checks the token's
audclaim. ThunderID accepts the token whenaudcontains this ThunderID instance's own issuer. - Validates
exp(andnbfif present) with the configured clock-skew leeway. - Extracts the
subclaim and non-standard claims as user attributes.
If any step fails, the exchange returns HTTP 400 with error=invalid_request and an "Invalid subject_token" message.
Attribute Configuration
Attribute configuration for token exchange uses the same attributeConfiguration structure as federated sign-in. See Add an OIDC Identity Provider for the full reference.
Limitations
- Only JWT tokens are supported. Opaque tokens (for example, GitHub access tokens) cannot be exchanged.
- The external user's
subis passed through as-is. No local user lookup or just-in-time provisioning occurs. - No scope mapping is applied; external token scopes use the existing intersection logic. External claims can be renamed to local attributes via the IdP's
attributeConfiguration(see Attribute Configuration); the resulting attributes are still filtered by the application'suserAttributesconfiguration. - Token exchange is available to all applications that have the token exchange grant type. No per-application IdP restriction applies.
Next Steps
- Manage Identity Providers: Create, update, and delete identity providers using the API.
- Connect an IdP to an Application: Use an IdP for redirect-based sign-in flows.
- Token Exchange: Learn about the RFC 8693 token exchange grant type in ThunderID.