Skip to content

feat(MCD): implement multiple custom domains support#368

Open
developerkunal wants to merge 8 commits intomasterfrom
feat/mcd-multiple-custom-domains
Open

feat(MCD): implement multiple custom domains support#368
developerkunal wants to merge 8 commits intomasterfrom
feat/mcd-multiple-custom-domains

Conversation

@developerkunal
Copy link
Contributor

📝 Checklist

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

🔧 Changes

This PR implements multi-issuer/multi-tenant support for applications that need to validate JWTs from multiple OIDC issuers (Multiple Custom Domains).

New Components

MultiIssuerProvider:

  • NewMultiIssuerProvider(opts ...MultiIssuerProviderOption) - Creates multi-issuer JWKS provider
  • KeyFunc(ctx context.Context) (any, error) - Automatically routes JWKS requests per issuer
  • ProviderCount() int - Returns number of cached providers

New JWKS Options:

  • WithMultiIssuerCacheTTL(ttl time.Duration) - Configure cache refresh interval (default: 15 min)
  • WithMultiIssuerHTTPClient(client *http.Client) - Custom HTTP client (default: 30s timeout)
  • WithMultiIssuerCache(cache Cache) - Custom cache implementation for Redis
  • WithMaxProviders(maxProviders int) - LRU eviction limit for memory control (default: unlimited)

Validator Options:

  • WithIssuers(issuers []string) - Accept tokens from multiple static issuers
  • WithIssuersResolver(resolver func(context.Context) ([]string, error)) - Dynamic issuer resolution

Context Helpers:

  • IssuerFromContext(ctx context.Context) (string, bool) - Extract validated issuer
  • SetIssuerInContext(ctx context.Context, issuer string) - Store issuer in context

Features

  • Automatic per-issuer JWKS routing based on validated issuer from JWT
  • Lazy provider creation with double-checked locking for thread safety
  • LRU eviction for memory management in high-tenant scenarios
  • Custom cache support (Redis) for distributed JWKS caching
  • Proactive background refresh at 80% TTL

Usage

Static Multiple Issuers:

provider, _ := jwks.NewMultiIssuerProvider(
    jwks.WithMultiIssuerCacheTTL(5*time.Minute),
)

validator.New(
    validator.WithKeyFunc(provider.KeyFunc),
    validator.WithIssuers([]string{
        "https://tenant1.auth0.com/",
        "https://tenant2.auth0.com/",
    }),
)

Large-Scale with Redis + LRU:

provider, _ := jwks.NewMultiIssuerProvider(
    jwks.WithMultiIssuerCache(redisCache),
    jwks.WithMaxProviders(1000),
)

Dynamic Issuer Resolution:

validator.New(
    validator.WithKeyFunc(provider.KeyFunc),
    validator.WithIssuersResolver(func(ctx context.Context) ([]string, error) {
        tenant := ctx.Value("tenant").(string)
        return db.GetIssuersForTenant(ctx, tenant)
    }),
)

📚 References

Use Cases:

  • Multi-tenant SaaS applications where each tenant has their own Auth0 domain
  • Domain migrations supporting both old and new domains simultaneously
  • Enterprise deployments with multiple Auth0 tenants

Scaling Recommendations:

  • < 10 issuers: Default in-memory cache
  • 10-100 issuers: In-memory with monitoring
  • 100-1000 issuers: Redis + WithMaxProviders(500)
  • 1000+ issuers: Redis + WithMaxProviders(1000)

🔬 Testing

Test Coverage: 96.8%

Unit Tests:

  • MultiIssuerProvider with all configuration options
  • Provider creation, caching, and LRU eviction
  • Concurrent access and thread safety
  • Custom cache integration
  • Error handling

Integration Tests:

  • examples/http-multi-issuer-example - Static issuer list with 3 tenants
  • examples/http-dynamic-issuer-example - Dynamic issuer resolution
  • examples/http-multi-issuer-redis-example - Redis cache with LRU eviction

CI/CD:

  • All examples tested in GitHub Actions workflow
  • New make test-examples target
  • Tests pass on ubuntu-latest, macos-latest, windows-latest

Manual Testing:

cd examples/http-multi-issuer-example
go run main.go

# Test with different tenant tokens
curl -H "Authorization: Bearer <tenant1-jwt>" http://localhost:3000
curl -H "Authorization: Bearer <tenant2-jwt>" http://localhost:3000

Documentation:

  • README with multi-tenant setup guide
  • Package docs with usage examples and performance benchmarks
  • Example READMEs with detailed instructions

  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
Copilot AI review requested due to automatic review settings January 15, 2026 09:44
@developerkunal developerkunal requested a review from a team as a code owner January 15, 2026 09:44
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 MultiIssuerProvider for 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 fetchMu mutex 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.

@developerkunal developerkunal changed the base branch from master to v3 January 15, 2026 09:54
@codecov-commenter
Copy link

codecov-commenter commented Jan 15, 2026

Codecov Report

❌ Patch coverage is 95.75290% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.35%. Comparing base (f643eb3) to head (78b1e09).

Files with missing lines Patch % Lines
jwks/multi_issuer_provider.go 88.31% 6 Missing and 3 partials ⚠️
jwks/provider.go 97.29% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #368      +/-   ##
==========================================
- Coverage   96.55%   96.35%   -0.20%     
==========================================
  Files          18       20       +2     
  Lines        1508     1727     +219     
==========================================
+ Hits         1456     1664     +208     
- Misses         34       41       +7     
- Partials       18       22       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@developerkunal developerkunal force-pushed the feat/mcd-multiple-custom-domains branch from 0d24351 to 2092669 Compare January 15, 2026 13:17
@developerkunal developerkunal force-pushed the feat/mcd-multiple-custom-domains branch 2 times, most recently from 3e3e3c3 to a8a4c83 Compare January 15, 2026 15:45
   - 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
@developerkunal developerkunal force-pushed the feat/mcd-multiple-custom-domains branch from a8a4c83 to e5877dd Compare January 15, 2026 20:34
Base automatically changed from v3 to master January 19, 2026 09:20
developerkunal and others added 5 commits January 21, 2026 23:45
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants