Validating JWTs from Multiple Issuers
Modern backend services rarely receive tokens from a single source. A B2B SaaS platform might accept user tokens from enterprise SSO connections alongside its own authentication layer. An internal API gateway might validate tokens from both a user-facing CIAM provider and a machine-to-machine OAuth server. A platform migrating between identity providers might need to honour tokens from the old issuer and the new one simultaneously.
Each of these scenarios forces the same architectural question: how do you validate a JWT correctly when the issuer varies by request? The answer is straightforward — but the failure modes if you get it wrong are not.
How JWT Validation Works
If you want to know more about how a JWT is structured and how to verify it, check our article on how to verify a JWT.
Validating a JWT means answering four questions in order:
- Is the algorithm acceptable? Only allow the algorithms you expect. Never accept
alg: none. - Is the signature valid? Verify the signature against the public key associated with the token’s
kid, fetched from the issuer’s JWKS endpoint. - Are the claims valid? Check
exp,nbf,iss, andaud— every one of them. - Is the token intended for this service? The
audclaim must match your service’s identifier.
With a single issuer, step 2 is trivial: you know exactly where to fetch the JWKS. With multiple issuers, you need a deliberate strategy for mapping a token to the right key material — and that mapping is where most mistakes happen.
The Right Approach: A Pre-Configured Issuer Registry
The correct mental model is to treat your service’s set of trusted issuers as a closed list, configured at deploy time, not derived at runtime from the token itself.
Each trusted issuer has a fixed entry in your configuration, carrying four things: the exact issuer URI (iss value), the pre-configured JWKS endpoint, the expected audience for this service, and the list of acceptable signing algorithms. Nothing in this list can be introduced or modified by a request.
The validation flow for every incoming token then follows the sequence shown below:

- Decode the JWT header and payload (without verifying yet)
- Read the
issclaim from the payload - Look up the issuer in your registry → if not found, reject immediately
- Retrieve the JWKS URI from the pre-configured entry for that issuer
- Fetch the JWKS and find the key matching the token’s
kid - Verify the signature using only that key
- Validate
exp,nbf,audusing the per-issuer expected audience - Accept or reject based on claim validity
The critical constraint is shown in purple in the diagram: the JWKS URI comes from your configuration, never from the token. The iss value in the token is only used as a lookup key into your registry — it never drives any network request on its own.
Caching JWKS Correctly
JWKS endpoints publish public keys that rotate over time. Cache aggressively (an hour TTL is typical), but implement a key-rotation fallback: on a signature verification failure with a cached key, invalidate the cache and re-fetch once before rejecting the token. This handles key rotation without opening a window for cache poisoning. The JWKS URI used for that re-fetch still comes from your configuration — not from the token.
Security Vulnerabilities from Incorrect Multi-Issuer Handling
Each deviation from the pattern above corresponds to a real attack class.
1. Dynamic JWKS Resolution from the Token (jku / iss Injection)
The most critical vulnerability is letting the token dictate where to fetch its own verification keys.

On the left (correct) side, the JWKS URI comes from the issuer registry in config. On the right (vulnerable) side, the service constructs the JWKS URL from the iss claim in the token, or from the jku (JWK Set URL) header parameter that the JWT standard optionally allows.
The attack is straightforward: an attacker crafts a token with iss: "https://attacker.com" or sets jku: "https://attacker.com/keys", hosts their own JWKS at that URL, and signs the token with their private key. The service fetches the attacker’s public key to “verify” the token, the signature check passes, and the token is accepted.
The fix: never construct network requests from token-derived values. Explicitly disable jku and x5u header processing in your JWT library. Use only pre-configured jwks_uri values.
2. Pooled Key Sets Across Issuers
The diagram below shows the difference between correct per-issuer key binding and the vulnerable pooled-key pattern.

On the left, each issuer’s configuration is bound exclusively to that issuer’s key set. On the right, all public keys from all issuers are merged into a single pool, and signature verification runs against the pool without preserving which key belongs to which issuer.
The attack: a legitimate token from issuer A, scoped to audience A, is verified successfully. But if the iss claim validation happens after signature verification and only checks that iss is any trusted issuer, an attacker can reuse a valid token from issuer A and modify the iss claim to issuer B. More subtly, if two issuers happen to share a key, a token from one can be accepted as if it came from the other.
The fix: each key must be bound to its issuer at verification time. Validate signature and iss atomically using only the keys belonging to the configured issuer.
3. Prefix or Wildcard Issuer Matching
The issuer comparison must be exact string equality — no prefix, suffix, or regex matching. If https://auth.acme.com is trusted and the check uses startsWith, an attacker registers https://auth.acme.com.evil.com and the prefix test passes. The issuer iss claim in a JWT is a URI; treat it as a case-sensitive, exact-match string with no ambiguous normalization.
4. Algorithm Confusion
The allowed algorithms must come from your issuer configuration, never from the token’s alg header. The two classic variants are alg: none (which disables signature verification entirely) and the RS256→HS256 confusion attack: since RS256 public keys are public by definition, an attacker who sets alg: HS256 can cause vulnerable libraries to use the RS256 public key as the HMAC secret — a value the attacker already knows — to produce a valid HMAC signature.
Each issuer entry in your registry declares exactly which algorithms are acceptable for tokens from that issuer. Reject anything that does not match, regardless of what alg the token claims.
5. Missing or Incorrect Audience Validation
Validating iss and the signature without checking aud leaves a critical gap. A token legitimately issued by a trusted issuer for another service — say, your billing dashboard — can be presented to your API. The issuer is trusted, the signature is valid, the expiry is in the future. Without audience validation, your API accepts a token that was never meant for it.
This is particularly dangerous in multi-tenant platforms where the same IdP issues tokens for multiple services and the audience is the only per-service discriminator.
Each issuer entry carries its own expected audience value. A token valid for api://billing must not be accepted by api://provisioning.
Checklist
Before shipping multi-issuer JWT validation, verify each point:
- Trusted issuers are configured statically — no issuer can be introduced at runtime by a token
- The JWKS URI for each issuer is pre-configured — never derived from
iss,jku, orx5u - Issuer comparison uses exact string equality — no prefix, suffix, or regex matching
- Keys are scoped per issuer — never pooled across issuers during signature verification
- Allowed algorithms are configured per issuer — never read from the token’s
algheader -
audis validated on every token, using the per-issuer expected audience -
expandnbfare always checked - JWKS caching includes a key-rotation retry on signature failure
-
jkuandx5uheader processing is explicitly disabled in your JWT library
Summary
Supporting multiple issuers is not inherently risky. The risk comes from letting the token drive any part of its own validation — which key to fetch, which algorithm to use, which issuer to trust. Move all of that configuration to your server. The token is untrusted input; your issuer registry is trusted configuration. Keep them separate, and multi-issuer validation is as safe as single-issuer validation.