Skip to main content

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.enabled is false, ThunderID does not serve the jwt-bearer grant. The grant, urn:ietf:params:oauth:grant-type:jwt-bearer, must also be in oauth.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​

  1. In the Console, navigate to Connections.
  2. Select Add custom connection.
  3. In the wizard, choose the connection type Trusted token issuer.
  4. Name the connection and continue.
  5. In the Add trusted issuer form, fill in:
    • Issuer URI: the external identity provider's issuer identifier. ThunderID matches this against the iss claim of incoming assertions.
    • JWKS endpoint: the URL ThunderID fetches the identity provider's public signing keys from, used to verify assertion signatures.
  6. 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.
  7. 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.
  8. Click Create connection.
note

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.

Add urn:ietf:params:oauth:grant-type:jwt-bearer to the application's grant types and keep it a confidential client.

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 headerRequirement
typ (header)Exactly oauth-id-jag+jwt
issMatches a connection registered as a trusted ID-JAG issuer
SignatureVerifiable against that connection's JWKS endpoint
audExactly one value, equal to the ThunderID issuer identifier, not the token endpoint URL
client_idEqual to the authenticated client's id
subPresent
exp, iat, jtiPresent. ThunderID accepts each jti once. See Limitations.
scopeOptional. 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.
resourceOptional. 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:

bash
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​

ParameterRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:jwt-bearer
assertionYesThe ID-JAG JWT issued by a trusted external identity provider
scopeNoNarrows the assertion's scope claim by intersection
resourceNo, repeatableMust be a subset of the assertion's resource claim; selects one resource server when the assertion authorizes several

Response​

FieldValue
access_tokenThe issued access token
token_typeBearer, or DPoP when the request includes a DPoP proof
expires_inToken lifetime in seconds. See Token lifetime
scopeThe 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:

  1. Checks the typ header is oauth-id-jag+jwt.
  2. Looks up a trusted ID-JAG issuer connection matching the assertion's iss claim. If none matches: invalid_grant - The assertion issuer is not registered as a trusted ID-JAG issuer.
  3. Verifies the signature against that connection's JWKS endpoint.
  4. Validates the time claims. If the assertion has expired: invalid_grant - The assertion has expired.
  5. Checks the aud claim is exactly one value equal to this server's issuer. If not: invalid_grant - The assertion audience must be exactly this server's issuer.
  6. Checks client_id matches the authenticated client. Any other claim failure returns invalid_grant - Invalid assertion.
  7. Confirms the sub claim is present.
  8. Records the assertion's jti so it cannot be redeemed twice. A replayed assertion returns invalid_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.

json
{
"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 jti until the assertion expires and rejects a second redemption with invalid_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 sub is 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.
note

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​

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.