Extra JWT Issuers#
--extra-jwt-issuers lets oauth2-proxy accept signed JWT bearer tokens from additional OIDC issuers beyond the primary provider. It only activates when --skip-jwt-bearer-tokens is also set. Each entry is an issuer=audience pair; the issuer must expose a .well-known/openid-configuration or .well-known/jwks.json endpoint.
How It Works#
When --skip-jwt-bearer-tokens is enabled, buildSessionChain in oauthproxy.go builds a list of TokenToSessionFunc loaders. The first slot is always provider.CreateSessionFromToken (the main OIDC provider); each extra issuer appends a loader built from CreateTokenToSessionFunc in pkg/apis/middleware/session.go.
CreateTokenToSessionFunc extracts a fixed set of claims (sub, email, email_verified, preferred_username, groups) from the verified token, then returns a SessionState . This is the root cause of most parity gaps — the primary OIDC path uses the richer, configurable buildSessionFromClaims path instead.
Verifiers are constructed in newVerifierFromJwtIssuer inside pkg/validation/options.go, which attempts OIDC discovery first and falls back to /.well-known/jwks.json if discovery fails. issuer=audience strings are parsed by parseJwtIssuers in the same file.
Known Bugs and Config Parity Gaps#
1. --oidc-groups-claim ignored — groups claim hardcoded (open)#
CreateTokenToSessionFunc hard-codes "groups" as the JSON tag . When --oidc-groups-claim is set to a custom name (e.g. ADGroups), tokens from extra issuers silently produce an empty SessionState.Groups, causing --allowed-group checks to fail with a generic 403 — with no log line indicating the claim was dropped.
PR #3540 adds an extractGroups function that reads the configured claim name instead of the hardcoded one.
2. Dot-separated --oidc-groups-claim paths not resolved (open)#
The main OIDC path uses getClaimFrom in pkg/providers/util/claim_extractor.go, which supports dot-notation paths (e.g. realm_access.roles) for nested claims. The extra-issuer path does a flat top-level key lookup only, so dotted paths always resolve to nothing.
There is also a secondary question of strictness: the main path uses CoerceClaim (lenient — coerces mixed arrays and objects to strings), while the post-#3540 extra-issuer path returns an error on a badly shaped claim. The appropriate behaviour for authorization input is deliberately left as a decision point.
3. --oidc-enabled-signing-alg ignored — ES256 tokens rejected (open PR)#
newVerifierFromJwtIssuer builds ProviderVerifierOptions without populating SupportedSigningAlgs, so the verifier defaults to RS256. Tokens signed with any other algorithm (e.g. ES256) are rejected from extra issuers even when --oidc-enabled-signing-alg=ES256 works for the primary issuer.
PR #3472 (open) fixes this by threading o.Providers[0].OIDCConfig.EnabledSigningAlgs into a new supportedSigningAlgs parameter on newVerifierFromJwtIssuer.
4. --insecure-oidc-allow-unverified-email not respected (open)#
The primary OIDC path honours AllowUnverifiedEmail. CreateTokenToSessionFunc unconditionally rejects tokens where email_verified is false, regardless of the flag. A community fix was described in issue #3087 but no merged PR exists.
5. Custom email/user claim names not applied (open PR)#
Provider-level custom claim names for email and user (not just groups) are also ignored by the extra-issuer path. PR #3436 (open) addresses this more broadly by removing the default parsing in CreateTokenToSessionFunc and routing through the provider's custom-claim logic instead, touching providers/oidc.go, providers/provider_data.go, providers/provider_default.go, and oauthproxy.go.
6. --allowed-groups not enforced for extra-issuer tokens (deferred to v8)#
Even when groups are correctly extracted, group-based authorization is delegated to provider.Authorize, which uses the main provider's logic. Mixing --allowed-groups with --extra-jwt-issuers creates potential cross-issuer trust issues and is explicitly deferred to the v8 redesign. See issue #1243.
Roadmap: v8 Bearer Auth Provider Chain#
The long-term fix for all of the above is tracked in issue #835. The plan is to separate the human cookie session provider from the bearer session provider and allow a chain of independently configured OIDC providers for bearer/machine-to-machine auth, each with its own issuer, audience, signing algorithms, claim mappings, and group rules. Existing --extra-jwt-issuers users would migrate to a chain of per-issuer OIDC provider configs.
Key Source Locations#
| Concern | File | Lines |
|---|---|---|
Flag parsing (issuer=audience) | pkg/validation/options.go | 128–167 |
| Session chain wiring | oauthproxy.go | 411–426 |
| Default JWT→session claim extraction | pkg/apis/middleware/session.go | 19–64 |
| Claim extractor (main OIDC path) | pkg/providers/util/claim_extractor.go | — |
CoerceClaim / toStringSlice | pkg/util/util.go | — |