Multi-IdP token validation with OpenID Connect

Uses: Kong Gateway

If your APIs serve clients from multiple identity providers (IdPs), (for example, employees using Okta, B2B partners using Azure AD, or legacy systems on an in-house IdP), the OpenID Connect (OIDC) plugin can act as a federated authentication broker at the gateway layer. In this setup, backends don’t need per-IdP validation logic. Kong Gateway centralizes auth policy and forwards only the verified identity context upstream.

The OIDC plugin supports two approaches for multi-IdP authentication, both using JWT access token (bearer) auth. Clients authenticate against their respective IdPs and present the resulting bearer token to Kong Gateway:

Trusted issuers registry

Token exchange

How it works Validates tokens from multiple issuers against their JWKS endpoints. Backends receive the original, unmodified token. Exchanges incoming tokens for a canonical token from one trusted target issuer. Backends only ever see tokens from that issuer.
When to use Token formats are consistent across IdPs. Backends can accept tokens from different issuers. Backends must trust a single issuer. Token formats differ across IdPs. You need downscoping, normalization, or cross-domain federation.
IdP requirements No special grant needed. IdPs must support RFC 8693 token exchange.
Min version Any 3.14
Key config parameters config.issuers_allowed, config.extra_jwks_uris config.token_exchange.subject_token_issuers

Option 1: Trusted issuers registry

In this approach, Kong Gateway acts as a federated authentication broker maintaining a registry of trusted issuers and their public key endpoints. The plugin inspects the iss claim of an incoming bearer token, looks up the matching JWKS endpoint from the configured list, validates the token signature and standard claims, then forwards the verified request upstream. No token transformation occurs.

 
sequenceDiagram
    participant C as Client
    participant K as API Gateway
with OIDC plugin participant IdPA as IdP A
(primary issuer) participant IdPB as IdP B
(via extra_jwks_uris) participant U as Upstream
(backend service) C->>K: Request with bearer token activate K K->>K: Extract iss claim from token alt If iss matches IdP A K->>IdPA: Fetch JWKS (if not cached) IdPA-->>K: Public keys else If iss matches IdP B K->>IdPB: Fetch JWKS (if not cached) IdPB-->>K: Public keys end K->>K: Verify signature K->>K: Check iss against issuers_allowed K->>U: Proxy request with original token activate U U-->>K: Response deactivate U K-->>C: Response deactivate K

Configure the following OIDC plugin settings:

  • config.issuers_allowed: Allowlist of issuer URLs the plugin will accept. Add every IdP’s issuer URL here, exactly as it appears in the iss claim of their tokens.
  • config.extra_jwks_uris: Additional JWKS endpoints for each IdP beyond the primary config.issuer. The plugin checks the primary discovery JWKS first, then falls back to these.

This approach works best when tokens issued by each IdP follow the same claim naming conventions.

The plugin uses config.issuer for discovery and to identify the primary issuer. Tokens from other IdPs will fail iss claim verification unless you set config.verify_claims to false and control allowed issuers via config.issuers_allowed instead.

If you update config.extra_jwks_uris after the plugin is already configured, clear the discovery cache for the change to take effect.

Configuration example

The following example configures the OIDC plugin to accept tokens from two identity providers. The first IdP is the primary config.issuer, while the second is added via config.extra_jwks_uris:

config:
  issuer: https://idp-a.example.com
  auth_methods:
    - bearer
  extra_jwks_uris:
    - https://idp-b.example.com/oauth2/v1/keys
  issuers_allowed:
    - https://idp-a.example.com
    - https://idp-b.example.com
  verify_signature: true
  verify_claims: false

In this example, a client authenticated with idp-a presents a bearer token. Kong Gateway validates it against idp-a’s JWKS and checks the issuer against config.issuers_allowed. The same flow applies to a client from idp-b. Both reach the upstream service without any token transformation.

For more detail and a complete walkthrough:

Option 2: Token exchange v3.14+

Token exchange is a standard, protocol-driven way to swap an incoming security token for a new one. Using JWT access token authentication, the plugin validates the incoming bearer token (the “subject token”), then uses its own client credentials to exchange it with the target issuer per RFC 8693. The upstream service receives the exchanged token from the target issuer, regardless of which IdP the client originally authenticated with.

 
sequenceDiagram
    participant C as Client
(e.g. mobile app) participant K as API Gateway
with OIDC plugin participant A as Authorization server
(e.g. Keycloak) participant U as Upstream
(backend service,
e.g. httpbin) C->>K: Request with subject token activate K note over K: Validate subject token
(iss, exp, nbf) K->>A: Token exchange request activate A A-->>K: Exchanged access token deactivate A K->>K: Validate exchanged token K->>U: Proxy request with exchanged token activate U U-->>K: Response deactivate U K-->>C: Response deactivate K

This approach also unlocks use cases beyond multi-IdP:

  • Downscoping: Exchange a high-privilege token for one with fewer scopes before forwarding to a less-trusted upstream.
  • Cross-domain federation: Exchange tokens when clients use one IdP but your APIs are protected by another.
  • Token translation: Convert tokens with different claim structures into a consistent internal format that your microservices understand.
  • On-behalf flows: Set up delegation and impersonation, where one client acts on behalf of another.

Key configuration parameters:

  • config.token_exchange.subject_token_issuers: Explicit list of trusted input issuers. The plugin only exchanges tokens whose iss claim matches an entry here.
  • config.token_exchange.conditions: Optional per-issuer rules that control when to trigger the exchange. If the subject token issuer and the target issuer (the one configured in config.issuer) are different, exchange always triggers. If they match, conditions determine whether to exchange.
  • config.token_exchange.request: The scopes and audience to request in the exchanged token.

With token exchange, trust is strictly enforced on both sides. Kong Gateway only exchanges tokens whose issuer is explicitly listed in subject_token_issuers. Each IdP in the list must also authorize Kong Gateway as a trusted client eligible for the token exchange grant.

Configuration example

The following example configures Kong Gateway to act as kong-client at the target issuer (idp-a) and exchange tokens issued by idp-b:

config:
  issuer: https://idp-a.example.com
  client_id:
    - kong-client
  client_secret:
    - ${kong-client-secret}
  client_auth:
    - client_secret_post
  auth_methods:
    - bearer
  token_exchange:
    subject_token_issuers:
      - issuer: https://idp-b.example.com

When a client from idp-b presents a bearer token, Kong Gateway validates it, then exchanges it with idp-a to produce a token the upstream service trusts. Tokens already issued by idp-a are validated as-is unless conditions require an exchange.

For more detail, see:

Troubleshooting

The following errors can appear in the Kong Gateway error log whether you’re using one issuer or multiple. The fix differs depending on which approach you’re using. For general debugging steps, see Debugging the OIDC plugin.

Error

Log message

Likely cause

Expired token invalid exp claim (<timestamp>) was specified for access token The token’s exp claim is in the past. Get a new token from the IdP.
Signature verification failure invalid signature (pkey:verify: ...) The signing key for the token’s issuer isn’t available to the plugin.
  • If using a trusted issuer registry, check that the issuer’s JWKS endpoint is listed in config.extra_jwks_uris and that the discovery cache has been cleared after any config change.
  • If using token exchange, confirm that config.token_exchange.subject_token_issuers includes the token’s iss value.

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!