feat(MCD): implement multiple custom domains support#368
Open
developerkunal wants to merge 15 commits intomasterfrom
Open
feat(MCD): implement multiple custom domains support#368developerkunal wants to merge 15 commits intomasterfrom
developerkunal wants to merge 15 commits intomasterfrom
Conversation
Implement complete multi-issuer/multi-tenant support for applications with multiple custom domains: MultiIssuerProvider: - Automatic JWKS routing per issuer from request context - Lazy provider creation with double-checked locking pattern - Thread-safe concurrent access across multiple issuers - LRU eviction with WithMaxProviders() for memory control - Custom cache support with WithMultiIssuerCache() (Redis, etc.) Validator Enhancements: - WithIssuers() for static multiple issuer lists - WithIssuersResolver() for dynamic issuer resolution - Issuer context management for provider routing JWKS Package: - Consolidated option.go for all provider options - MultiIssuerProvider with configurable TTL and HTTP client - Per-issuer provider caching with LRU eviction - Redis cache support for 100+ tenant scenarios Examples: - http-multi-issuer-example: Static issuer configuration - http-dynamic-issuer-example: Dynamic issuer resolution - http-multi-issuer-redis-example: Large-scale Redis + LRU Testing & CI: - 96.8% code coverage maintained - Integration tests for all examples - CI workflow automation for example validation Documentation: - Complete multi-tenant guide in README - Scaling recommendations for different tenant counts - Performance benchmarks and best practices - Enhanced package documentation
There was a problem hiding this comment.
Pull request overview
This PR implements multi-issuer/multi-tenant support for applications that need to validate JWTs from multiple OIDC issuers (Multiple Custom Domains).
Changes:
- Adds
MultiIssuerProviderfor automatic per-issuer JWKS routing with lazy provider creation, LRU eviction, and custom cache support - Migrates from go-jose/go-jose.v2 to lestrrat-go/jwx/v3 for JWKS handling
- Introduces DPoP (Demonstrating Proof-of-Possession) support with trusted proxy configuration and multiple operation modes
Reviewed changes
Copilot reviewed 80 out of 112 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| jwks/provider.go | Refactored JWKS provider to use jwx library, added caching layer with background refresh |
| jwks/option.go | New options for provider configuration supporting multi-issuer and caching customization |
| jwks/multi_issuer_provider_test.go | Comprehensive tests for multi-issuer provider including concurrency and LRU eviction |
| jwks/multi_issuer_provider.go | Core implementation of multi-issuer provider with double-checked locking and LRU cache |
| jwks/doc.go | Package documentation explaining provider types, usage patterns, and performance guidance |
| internal/oidc/oidc.go | Minor change to defer function for response body close |
| internal/oidc/doc.go | New documentation for OIDC discovery functionality |
| go.mod | Updated to v3 module path and migrated to jwx library |
| extractor_test.go | Enhanced tests for token extraction including DPoP scheme support |
| extractor.go | Extended token extractor to support DPoP scheme and return scheme information |
| examples/* | Multiple new examples demonstrating multi-issuer, dynamic issuer resolution, Redis caching, DPoP modes, and trusted proxy configuration |
| Makefile | Added test-examples target and updated golangci-lint version |
Comments suppressed due to low confidence (1)
jwks/provider.go:1
- The comment mentions 'single-flight fetching' behavior, but this is implemented through the
fetchMumutex which blocks all concurrent fetches rather than allowing one to proceed while others wait for the result. This is not true single-flight behavior (like sync.Singleflight). Consider clarifying the comment to say 'serialized fetching' or 'mutex-protected fetching' to accurately describe the implementation.
package jwks
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #368 +/- ##
==========================================
- Coverage 96.55% 95.10% -1.46%
==========================================
Files 18 20 +2
Lines 1508 1878 +370
==========================================
+ Hits 1456 1786 +330
- Misses 34 62 +28
- Partials 18 30 +12 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
0d24351 to
2092669
Compare
3e3e3c3 to
a8a4c83
Compare
- Adjust timing assertion to accept 3-4 requests (timing variance) - Increase TTL from 2s to 5s to prevent cache expiry during test - Fixes CI failures in background refresh scenarios
a8a4c83 to
e5877dd
Compare
…idation - Merged GetWellKnownEndpointsWithIssuerValidation into GetWellKnownEndpointsFromIssuerURL - Added expectedIssuer parameter to perform double-validation by default - Issuer validation now mandatory (defense-in-depth for MCD security) - Updated all callers in Provider and CachingProvider - Updated all test mocks to include issuer field in OIDC metadata - Simplified internal API from 2 functions to 1 function - All tests passing (internal/oidc + jwks)
- Bump from v0.10.3 to v0.10.5 in http-dpop-trusted-proxy, http-jwks-example, and iris-example. - Update from v3.0.1 to v3.0.3 in http-dpop-trusted-proxy, http-jwks-example, iris-example, and dynamic-issuer-example. - Upgrade from v1.6.4 to v1.6.7 in http-dpop-trusted-proxy, http-jwks-example, and iris-example. - Upgrade from v0.45.0 to v0.46.0 in http-dpop-trusted-proxy, http-jwks-example, and iris-example. - Upgrade from v0.38.0 to v0.40.0 in http-dpop-trusted-proxy, http-jwks-example, and iris-example. - Update from v1.6.1 to v1.7.1 in iris-example. - Add issuer field to the OpenID configuration response in http-jwks-example.
… documentation - Added support for Cache-Control headers in JWKS caching provider to extend cache time when allowed. - Updated documentation to clarify security and performance features, including automatic validation of exp and nbf claims. - Improved examples and README files to reflect new caching strategies and recommendations for multi-tenant scenarios.
kishore7snehil
requested changes
Feb 16, 2026
…ntext - Refresh stale timestamp after acquiring fetch lock to prevent redundant JWKS fetches - Respect shorter Cache-Control max-age from IdP to support key rotation signals - Add io.LimitReader (1MB) on OIDC discovery response as defense-in-depth - Document WithCacheTTL zero-value behavior (uses default 15m) - Set issuer in context before resolver so it has access to unverified iss claim - Update test expectation to match corrected Cache-Control behavior
Add support for symmetric (HS256/HS384/HS512) issuers in MCD scenarios
and enforce algorithm policy before JWKS fetch to prevent algorithm
bypass when keyFunc returns jwk.Set.
- Replace single signatureAlgorithm with allowedAlgorithms slice
- Add WithAlgorithms option for mixed-algorithm MCD (e.g., RS256 + HS256)
- Enforce mutual exclusivity between WithAlgorithm and WithAlgorithms
- Validate token alg header against allowed list before JWKS fetch (fail-fast)
- Add IssuerKeyConfig, WithIssuerKeyConfig, and WithIssuerKeyConfigs for
per-issuer symmetric key configuration
- Symmetric issuers bypass OIDC discovery via static jwk.Set lookup
- Use jws.Parse for single-pass JWS envelope parsing in ValidateToken
- Normalize fmt.Errorf to errors.New for static error strings
Add ProviderStats with per-issuer breakdown (OIDC vs symmetric, algorithm, last-used time) to help operators monitor and debug multi-tenant setups. Also fix ProviderCount to include symmetric issuers in the total.
88e1e95 to
e632087
Compare
kishore7snehil
requested changes
Feb 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Checklist
🔧 Changes
Implements Multiple Custom Domains (MCD) support — enabling multi-tenant applications to validate JWTs from multiple OIDC issuers, with symmetric key support and algorithm policy enforcement.
Types and Methods Added
JWKS Package — MultiIssuerProvider:
NewMultiIssuerProvider(opts...)— Creates multi-issuer JWKS provider with dynamic per-issuer routingMultiIssuerProvider.KeyFunc(ctx)— Routes JWKS requests to correct issuer based on contextMultiIssuerProvider.ProviderCount()— Total managed issuers (OIDC + symmetric)MultiIssuerProvider.Stats()— Per-issuer observability (type, algorithm, last-used time)IssuerKeyConfig— Per-issuer symmetric key configuration (Secret, Algorithm, KeyID)ProviderStats,IssuerInfo,IssuerType— Observability typesJWKS Package — New Options:
WithMultiIssuerCacheTTL(ttl)— Cache refresh interval (default: 15 min)WithMultiIssuerHTTPClient(client)— Custom HTTP clientWithMultiIssuerCache(cache)— Custom cache implementation (e.g., Redis)WithMaxProviders(max)— LRU eviction limit (default: 100)WithIssuerKeyConfig(issuer, config)— Per-issuer symmetric keyWithIssuerKeyConfigs(configs)— Batch symmetric key configurationValidator Package — New Options:
WithIssuers(issuers)— Accept tokens from multiple static issuersWithIssuersResolver(resolver)— Dynamic issuer resolution at request timeWithAlgorithms(algorithms)— Multiple allowed signature algorithmsValidator Package — Context Helpers:
IssuerFromContext(ctx)— Extract validated issuer from contextSetIssuerInContext(ctx, issuer)— Store issuer in contextTypes and Methods Changed
Validator.ValidateToken— Now usesjws.Parsefor single-pass header extraction; validates tokenalgagainst allowed list before JWKS fetchValidator.parseToken— Usesjws.WithUseDefault(true)forjwk.Setto support symmetric keys withoutkid; returns clear error when multiple algorithms configured with raw keyValidator.validate()— ChecksallowedAlgorithmsslice instead of singlesignatureAlgorithmWithAlgorithm— Now writes toallowedAlgorithmsslice; mutually exclusive withWithAlgorithmsInternal Field Changed (Non-Breaking)
Validator.signatureAlgorithm(unexported) →Validator.allowedAlgorithms(unexported slice)Summary of Usage
Static Multiple Issuers:
Mixed Symmetric + Asymmetric MCD:
Using
IssuerFromContextin Request Handlers:After token validation, the validated issuer is available in the request context. This lets handlers implement per-tenant logic:
Dynamic Issuer Resolution with
WithIssuersResolver:For applications where the allowed issuers are not known at startup — e.g., multi-tenant SaaS where tenants are stored in a database:
The resolver is called on every request with the token's unverified issuer already in context. This means you can do a targeted database lookup (
WHERE issuer = ?) instead of loading all issuers. The resolver must be:Tenant Monitoring:
📚 References
WithAlgorithm(RS256)silently ignored whenkeyFuncreturnsjwk.SetWithAlgorithmsfor mixed-algorithm MCD (RS256 + HS256 issuers)🔬 Testing
Coverage: validator 97.1%, jwks 96.2%
Algorithm enforcement (#379):
algheader rejected before JWKS fetchWithAlgorithmsaccepts multiple algorithms; mutual exclusivity withWithAlgorithmtestedSymmetric MCD:
WithIssuerKeyConfigreturns staticjwk.Set, bypasses OIDC discoveryObservability:
ProviderCountincludes symmetric issuersStats()returns correct per-issuer breakdown (type, algorithm, last-used)No regressions: All existing tests pass across all packages and all 12 example test suites.
Manual testing: