Accept Identity Assertions from an External IdP
Register an external identity provider as a trusted token issuer so ThunderID accepts the Identity Assertion Authorization Grants (ID-JAGs) it issues and exchanges them for ThunderID-issued access tokens.
When to Use This
Use this when an enterprise identity provider, such as Okta, Azure AD, or another ID-JAG-capable issuer, governs which clients can reach your APIs. Clients arrive at ThunderID already holding an ID-JAG from that identity provider instead of signing in to ThunderID directly. When the protected resource is an MCP server, this is an instance of the Enterprise-Managed Authorization pattern; see Enterprise-Managed Authorization for MCP.
Prerequisites
- A running ThunderID deployment. See Get Started.
- Console access.
- ID-JAG acceptance allowed on the deployment. It is on by default. If
oauth.id_jag.acceptance.enabledisfalse, ThunderID does not serve thejwt-bearergrant. The grant,urn:ietf:params:oauth:grant-type:jwt-bearer, must also be inoauth.allowed_grant_types. See the server configuration. - The external identity provider's issuer URI and JWKS endpoint.
- An application or an agent configured as described in Configure the Application or Agent.
How Acceptance Works
A client that already holds an ID-JAG from an external identity provider presents it to ThunderID using the jwt-bearer grant, and receives an access token without a separate sign-in step.
Register the Issuer
- In the Console, navigate to Connections.
- Select Add custom connection.
- In the wizard, choose the connection type Trusted token issuer.
- Name the connection and continue.
- In the Add trusted issuer form, fill in:
- Issuer URI: the external identity provider's issuer identifier. ThunderID matches this against the
issclaim of incoming assertions. - JWKS endpoint: the URL ThunderID fetches the identity provider's public signing keys from, used to verify assertion signatures.
- Issuer URI: the external identity provider's issuer identifier. ThunderID matches this against the
- Leave Enable token exchange at its default (on), or turn it off if you do not also want this issuer's tokens accepted for RFC 8693 token exchange. When token exchange is on, a Trusted token audience field appears; leave it empty for ID-JAG-only use cases.
- Turn on Enable Identity Assertion JWT Authorization Grant (ID-JAG). This toggle is off by default. If the deployment's discovery document does not list the ID-JAG grant profile, the Console shows a hint and does not let you turn it on.
- Click Create connection.
Enable token exchange and Enable Identity Assertion JWT Authorization Grant (ID-JAG) are independent trust settings. Enabling one does not enable the other.
Configure the Application or Agent
The client that presents ID-JAGs to ThunderID can be an application or an agent. It must be a confidential client, and its allowed grant types must include urn:ietf:params:oauth:grant-type:jwt-bearer.
- Application
- Agent
Add urn:ietf:params:oauth:grant-type:jwt-bearer to the application's grant types and keep it a confidential client.
An agent redeems ID-JAGs as a delegated agent, because the access token it receives acts for the user the assertion names.
- In the Console, navigate to Agents and select the agent.
- Open the Advanced tab.
- Under Operating mode, turn on Delegated mode. ThunderID adds the authorization code and refresh token grants by default. The JWT bearer option in Grant types stays locked until Delegated mode is on.
- In Grant types, select JWT bearer.
- In Grant types, clear authorization_code. Clearing it also removes refresh_token unless CIBA is selected, and without authorization code the agent needs no authorized redirect URIs or allowed user types before you can save. Keep it only if the agent also signs users in itself.
To configure the agent through the API, create it with POST /agents or update it with PUT /agents/{id}. Set delegated to true and include the JWT bearer grant type in the oauth2 entry of inboundAuthConfig. The JWT bearer grant counts as a delegation grant, so the agent needs no other grant beyond client_credentials. This combination is valid once the backend change that makes JWT bearer a delegation grant for agents is released:
{
"delegated": true,
"inboundAuthConfig": [
{
"type": "oauth2",
"config": {
"grantTypes": [
"client_credentials",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
],
"tokenEndpointAuthMethod": "client_secret_basic"
}
}
]
}
See the Agents API Reference for the full request.
A client registered from a Client ID Metadata Document qualifies only when its document declares the private_key_jwt authentication method. Its client ID is the document's URL, so the ID-JAG's client_id claim must carry that URL, and the client authenticates with a client assertion instead of a client secret. See Client Authentication Methods.
What the Issuer Must Send
The external identity provider's ID-JAG must satisfy these requirements for ThunderID to accept it:
| Claim or header | Requirement |
|---|---|
typ (header) | Exactly oauth-id-jag+jwt |
iss | Matches a connection registered as a trusted ID-JAG issuer |
| Signature | Verifiable against that connection's JWKS endpoint |
aud | Exactly one value, equal to the ThunderID issuer identifier, not the token endpoint URL |
client_id | Equal to the authenticated client's id |
sub | Present |
exp, iat, jti | Present. ThunderID accepts each jti once. See Limitations. |
scope | Optional. Bounds what can be granted. When the request also carries a scope parameter, the issued scope is the intersection of the two. When the request omits scope, this claim is the candidate in full, narrowed further only by the connection's Authorization Mapping when one is configured — see below. |
resource | Optional. When present, the request's resource parameter must be a subset of this claim. When absent, any valid resource is accepted. |
The client then presents the assertion to ThunderID's token endpoint using the jwt-bearer grant:
curl -X POST https://thunderid.example.com/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
-d assertion="$ID_JAG"
Request Parameters
| Parameter | Required | Value |
|---|---|---|
grant_type | Yes | urn:ietf:params:oauth:grant-type:jwt-bearer |
assertion | Yes | The ID-JAG JWT issued by a trusted external identity provider |
scope | No | Narrows the assertion's scope claim by intersection |
resource | No, repeatable | Must be a subset of the assertion's resource claim; selects one resource server when the assertion authorizes several |
Response
| Field | Value |
|---|---|
access_token | The issued access token |
token_type | Bearer, or DPoP when the request includes a DPoP proof |
expires_in | Token lifetime in seconds. See Token lifetime |
scope | The granted scope (intersection of the request scope and the assertion's scope) |
No issued_token_type or refresh_token is returned.
How Validation Works
ThunderID validates an incoming assertion in this order:
- Checks the
typheader isoauth-id-jag+jwt. - Looks up a trusted ID-JAG issuer connection matching the assertion's
issclaim. If none matches:invalid_grant-The assertion issuer is not registered as a trusted ID-JAG issuer. - Verifies the signature against that connection's JWKS endpoint.
- Validates the time claims. If the assertion has expired:
invalid_grant-The assertion has expired. - Checks the
audclaim is exactly one value equal to this server's issuer. If not:invalid_grant-The assertion audience must be exactly this server's issuer. - Checks
client_idmatches the authenticated client. Any other claim failure returnsinvalid_grant-Invalid assertion. - Confirms the
subclaim is present. - Records the assertion's
jtiso it cannot be redeemed twice. A replayed assertion returnsinvalid_grant-Invalid assertion.
Requests missing the assertion parameter fail with invalid_request - Missing required parameter: assertion. A public client attempting the grant fails with invalid_client - The jwt-bearer grant requires a confidential client. An application or agent without the jwt-bearer grant type fails with unauthorized_client - The client is not authorized to use this grant type. When the deployment does not serve the grant, because oauth.id_jag.acceptance.enabled is false or jwt-bearer is not in oauth.allowed_grant_types, every request fails with unsupported_grant_type - Unsupported grant type. A resource parameter that is not a subset of the assertion's resource claim fails with invalid_target - The resource parameter must be a subset of the assertion's resource claim; omitting resource when no default resource server is configured fails with invalid_target - No resource parameter supplied and no default resource server is configured.
The Tokens ThunderID Issues
The issued access token's sub claim is the assertion's subject verbatim, an external identifier rather than a local user id. An idp claim carries the assertion's issuer. ThunderID does not link this subject to a local account or run just-in-time provisioning for it, and no refresh token is issued.
It does apply the connection's Authorization Mapping when one is configured, resolving roles, groups, and permissions from the assertion's own claims into the granted scope, the same mapping and the same group-ancestor expansion a sign-in or a token exchange through that connection would use. A scope parameter in the request narrows the candidate scope by intersection; omitting it does not empty the grant, it leaves the assertion's scope claim as the candidate in full, which mapping then narrows to whatever it actually authorizes. If mapping is configured but fails to resolve, the request fails outright rather than falling back to no mapping, since there is no already-issued session to fall back to the way a sign-in has.
The token is DPoP-bound when the request includes a DPoP proof. A resource parameter must be a subset of the assertion's resource claim, selecting one resource server when the assertion authorizes several.
Actor claim
The access token carries an act (actor) claim naming the client that redeemed the assertion when that client is an agent, or an application with includeActClaim enabled. An application without includeActClaim receives no act claim. act.sub is the agent or application resource identifier, not the client ID. This is the same rule the authorization code and CIBA grants, and token exchange without an actor_token, follow. An act claim inside the ID-JAG is not carried into the issued token. A resource server reads sub for the external user the assertion names and act for the client that acted for them.
{
"sub": "<external-user-id>",
"idp": "<assertion-issuer>",
"act": { "sub": "<agent-or-application-id>" }
}
Token lifetime
The access token is valid for the client's user access token validity, the same setting the client's other user tokens use. Set it through the API as token.accessToken.userConfig.validityPeriod in the client's OAuth configuration. When a client created or updated through the API sets none, ThunderID fills it in on save: from the client's assertion.validityPeriod if set, otherwise from the server default jwt.validity_period in the server configuration, which is 3600 seconds unless changed. Changing the server default later does not change a client that is already saved. A client defined in a declarative resource file is stored as written, so with no userConfig validity it uses the current server default at runtime.
The token carries none of the user attributes selected for the client's user access token, because the subject is the external user and ThunderID holds no local account for it.
Limitations
- Each assertion is redeemable once. ThunderID records its
jtiuntil the assertion expires and rejects a second redemption withinvalid_grant. Request a fresh ID-JAG from the identity provider for each redemption, including a retry. - No local account mapping occurs, regardless of Authorization Mapping. The token's
subis the external identifier as-is; nothing links it to a local user record. - The Console's Authorization Mapping tab is not yet available on a connection created through the Trusted token issuer wizard, even though it is the same OIDC connection type and the backend applies its mapping the same way. Configure it through the Connections API directly until the Console adds it.
This page covers accepting assertions for token issuance. The Secure ThunderID Using a Third-Party Identity Provider guide is a different feature: it validates external tokens directly against ThunderID's own APIs without issuing a new token.
Next Steps
- Issue Identity Assertions (ID-JAG) - Configure ThunderID to exchange a user's ID token for an ID-JAG
- Identity Assertion Authorization Grant (ID-JAG) - Overview of both ID-JAG roles
- Token Exchange with External Identity Providers - RFC 8693 token exchange for external identity provider tokens
- Enterprise-Managed Authorization for MCP - Use ID-JAG to authorize MCP clients without a second sign-in