Secure ThunderID Using a Third-Party Identity Provider
By default, ThunderID only accepts tokens it issued itself. Your users might authenticate against an external authorization server: another ThunderID instance, an enterprise identity provider, or any standard OIDC provider. In that case, those users cannot call ThunderID's own APIs or sign in to the ThunderID Console unless ThunderID can validate their tokens.
The trusted issuer feature enables this. You configure ThunderID to trust a specific external authorization server, and ThunderID fetches that server's public signing keys to validate incoming tokens locally. ThunderID acts as the resource server: it accepts and validates tokens the external server issued, without issuing its own tokens for those users or duplicating the user directory.
You enable this by adding a server.security.trusted_issuer block to deployment.yaml on the resource-server instance.
When to Enable
Enable trusted issuer on a ThunderID instance when:
- Users authenticate against an external authorization server and need to call ThunderID's own APIs or sign in to the ThunderID Console using those tokens.
- You want ThunderID to act as a resource server for an external IdP without duplicating user directories or re-issuing tokens.
- Incoming bearer tokens are signed by the external server and carry an
issclaim that identifies it.
If a ThunderID instance only serves its own users and issues its own tokens, omit the server.security.trusted_issuer block.
Prerequisites
- A running ThunderID instance that acts as the resource server.
- An external authorization server (another ThunderID instance, an enterprise IdP, or any OIDC-compatible provider) that issues JWTs with an
issclaim and exposes a JWKS endpoint. - The central server's issuer URL and JWKS endpoint URL.
Configure the Server
Add a server.security.trusted_issuer block to the tenant instance's deployment.yaml:
server:
security:
trusted_issuer:
issuer: "https://cloud.example.com"
jwks_url: "https://cloud.example.com/oauth2/jwks"
audience: "https://tenant.example.com"
required_claims:
- claim: "ouId"
value: "tenant-123"
| Field | Required | Description |
|---|---|---|
issuer | Yes | The issuer URL of the central authorization server. ThunderID matches this against the iss claim on every incoming token and rejects any token that does not match exactly, including differences in scheme, host, port, or trailing slash. |
jwks_url | Yes | The JWKS endpoint on the central server. ThunderID fetches the public signing keys from this URL and uses them to verify token signatures. |
audience | Yes | The aud value that tokens from the central server must carry. Set this to the resource-server ThunderID instance's own public URL. The client at the central server must include a matching resource parameter in the authorization request so the issued token targets this instance. |
required_claims | No | A list of claims that every accepted token must contain, each paired with an expected value. Tokens that are missing a required claim or carry a different value are rejected. Use this to scope accepted tokens further, for example to a specific organization unit. |
The JWKS cache time-to-live applies to signing keys fetched from this endpoint and is configured at server.security.jwks_cache_ttl, not inside the trusted_issuer block. See Security Configuration for details.
Configure the Console
The server.security.trusted_issuer block controls how ThunderID validates incoming API tokens. It does not affect how users sign in to the ThunderID Console. By default, the Console still authenticates users against the local ThunderID instance.
To delegate Console sign-in to the central authorization server, add a trusted_issuer entry to the Console's runtime configuration at apps/console/config.js:
window.__THUNDERID_RUNTIME_CONFIG__ = {
// ...existing client, server, brand entries...
trusted_issuer: {
hostname: "auth.example.com",
port: 443,
http_only: false,
public_url: "https://auth.example.com",
client_id: "FEDERATED_CONSOLE",
scopes: ['openid', 'profile', 'email', 'group', 'ou', 'system', 'system:user', 'system:group', 'system:ou:view', 'system:usertype:view'],
type: "generic",
},
};
| Field | Description |
|---|---|
hostname | The hostname of the external authorization server the Console redirects users to for sign-in. |
port | The port on the external authorization server. |
http_only | Set to true only for local or development authorization servers served over HTTP. |
public_url | The fully qualified URL of the external authorization server. The Console uses this to build authorization requests. |
client_id | The OAuth client ID registered on the external authorization server for this ThunderID instance's Console. |
scopes | The scopes the Console requests from the external authorization server. Include every scope that ThunderID's APIs require. |
type | Set to "generic" when the central authorization server is a generic OIDC provider rather than another ThunderID instance. When set, the Console skips ThunderID-specific bootstrap calls (flow metadata, branding preferences) that would fail against a generic provider, and uses the provider's end_session_endpoint for sign-out. Defaults to "thunderid". |
When this block is present, the Console delegates sign-in to the external authorization server. When it is absent, the Console authenticates against the local ThunderID instance.
The external authorization server must permit this ThunderID instance's origin in its CORS allowed-origins configuration. Otherwise, browser calls from the Console to the authorization server's /oauth2/token, /oauth2/jwks, and related endpoints are blocked.
How Validation Works
When a request arrives with a bearer token, ThunderID reads the token's iss claim to decide which verifier to use. For a token whose iss matches trusted_issuer.issuer, ThunderID performs the following checks in order:
- Parses the JWT and confirms its
issclaim matchestrusted_issuer.issuer. - Fetches signing keys from
trusted_issuer.jwks_urland verifies the signature. - Checks the
expclaim and rejects expired tokens. - Checks the
nbf(not before) claim if present. Ifnbfis absent, the token is still accepted. - Confirms the
audclaim matchestrusted_issuer.audience. Theaudclaim may be a single string or a JSON array of strings; ThunderID accepts the token if the configured audience appears in either form. - Checks that every entry in
required_claimsis present with the expected value.
If any check fails, ThunderID rejects the token.
Claim Details
iss (Issuer): Must match trusted_issuer.issuer exactly, including scheme, host, and any path.
exp (Expiry): Required. Tokens that omit exp or carry a non-numeric value are rejected.
nbf (Not Before): Optional per RFC 7519 Section 4.1.5. Many commercial OIDC providers omit this claim. When nbf is present, ThunderID enforces it with the configured clock-skew leeway.
aud (Audience): May be a single string or an array of strings per RFC 7519 Section 4.1.3. Some providers issue an array aud when a token targets more than one audience, for example both the resource API and the provider's own /userinfo endpoint. ThunderID matches the configured trusted_issuer.audience value against both forms.
required_claims: Each entry matches by exact string equality. The claim must be present on the token and its value must equal the configured value.
Accepted signature algorithms: RS256, PS256, RS512, ES256, ES384, ES512, EdDSA. Tokens signed with any other algorithm are rejected.
Self-Issued Tokens
Configuring server.security.trusted_issuer does not stop ThunderID from accepting the tokens it issues itself. ThunderID selects the verifier based on the token's iss claim:
issmatchestrusted_issuer.issuer: the token is verified against the central server's JWKS endpoint.issmatches this ThunderID instance's own issuer (configured atjwt.issuer, defaulting to the instance's public URL): the token is verified with the local signing key.- Any other
issvalue, or a token whose payload cannot be parsed, is rejected.
This means service accounts, the system bootstrap token, and Console sessions that are not delegated all continue to work unchanged. Verification never falls back between verifiers: a token routed to the JWKS verifier that fails signature validation is rejected immediately. ThunderID does not retry it against the local signing key.
JWKS responses are cached in-process for server.security.jwks_cache_ttl seconds (default: 300). Plan key rotations on the central server with at least that much overlap so tenant instances do not reject tokens signed with a new key before the cache refreshes.
Helm Configuration
When installing ThunderID with the Helm chart at install/helm, the same settings are available under the configuration key using camelCase field names.
configuration:
server:
security:
trustedIssuer:
issuer: "https://auth.example.com"
jwksUrl: "https://auth.example.com/oauth2/jwks"
audience: "https://thunder.example.com"
requiredClaims:
- claim: "ouId"
value: "tenant-123"
To configure the Console without editing apps/console/config.js directly, set configuration.consoleClient.trustedIssuer in the chart values. The fields mirror those in Configure the Console but use camelCase:
configuration:
consoleClient:
trustedIssuer:
hostname: "auth.example.com"
port: 443
httpOnly: false
publicUrl: "https://auth.example.com"
clientId: "FEDERATED_CONSOLE"
scopes: "['openid', 'profile', 'email', 'group', 'ou', 'system', 'system:user', 'system:group', 'system:ou:view', 'system:usertype:view']"
type: "generic"
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Console sign-in redirect hangs or token exchange fails with a CORS error | ThunderID's origin is not in the external authorization server's CORS allowed-origins list | Add the ThunderID instance's origin to the authorization server's CORS configuration. |
| Tokens rejected with "issuer mismatch" | trusted_issuer.issuer does not exactly match the iss claim on incoming tokens | Set trusted_issuer.issuer to match exactly, including scheme, host, port, and trailing slash. |
| Tokens rejected with "audience mismatch" | The authorization server is not receiving a resource parameter and falls back to a default resource server whose identifier does not match ThunderID's expected audience | Configure the client to send resource=<server-url> in the authorization request, or set defaultResourceServer to the expected resource server. |
| Tokens rejected with "required claim missing" | A claim listed under required_claims is absent from the token or its value does not match | Remove the entry from required_claims, or configure the authorization server to include the claim with the expected value. |
| JWKS fetch fails | trusted_issuer.jwks_url is unreachable, or the authorization server uses a certificate that ThunderID does not trust | Verify network connectivity to the URL, and install the authorization server's CA certificate on the ThunderID instance if needed. |
| Console does not redirect to the external authorization server | consoleClient.trustedIssuer is not set, or publicUrl/hostname still point at the local ThunderID instance | Verify the consoleClient.trustedIssuer block and re-render the Helm chart. |
Related Guides
- Resource Indicators, use the
resourceparameter to set theaudclaim on tokens issued by the central authorization server. - Resource Servers, define resource servers with identifiers and permissions.
- Security Configuration, configure JWKS cache TTL and other security settings.