This example demonstrates how to use the JWT middleware with multiple issuers (multi-tenant) using a Redis-backed cache for JWKS.
IMPORTANT: For applications with 100+ tenants/issuers, it is strongly recommended to use a custom cache implementation like Redis instead of the default in-memory cache.
- Memory Efficiency: The default in-memory cache creates a separate cached JWKS entry per issuer, which can consume significant memory with many tenants
- Scalability: Redis allows you to scale to hundreds or thousands of tenants without memory concerns
- Shared Cache: Multiple application instances can share the same Redis cache, reducing redundant JWKS fetches
- Better Performance: Avoids memory pressure and potential GC issues in high-tenant scenarios
- Go 1.23 or later
- Redis server running on
localhost:6379(or modify the connection string in the code)
If you don't have Redis installed, you can run it with Docker:
docker run -d --name redis -p 6379:6379 redis:latestOr install Redis locally:
- macOS:
brew install redis && brew services start redis - Ubuntu:
sudo apt-get install redis-server && sudo systemctl start redis
-
Start Redis server (see above)
-
Run the example:
go run main.go- The server will start on
http://localhost:3000
Update the following in main.go:
issuers := []string{
"https://tenant1.auth0.com/",
"https://tenant2.auth0.com/",
// Add as many issuers as needed - Redis handles the memory management
}
audience := []string{"<your api identifier>"}- The
RedisCachestruct implements thejwks.Cacheinterface - When a JWT is validated:
- The middleware extracts the issuer from the token
- The
MultiIssuerProviderroutes the request to the correct issuer - The Redis cache checks if JWKS is cached for that issuer
- If not cached, it fetches from the OIDC provider and caches in Redis
- Subsequent requests for the same issuer use the cached JWKS from Redis
The example uses a 15-minute TTL for cached JWKS:
redisCache, err := NewRedisCache("localhost:6379", 15*time.Minute, nil)Adjust this based on your security requirements and how frequently your JWKS keys rotate.
For production deployments:
-
Redis Configuration: Use a production-ready Redis setup with:
- Authentication (
redis.Options{Password: "your-password"}) - SSL/TLS if connecting to remote Redis
- Redis Cluster for high availability
- Authentication (
-
Error Handling: The example includes basic error handling. Consider adding:
- Circuit breakers for Redis connection failures
- Fallback to in-memory cache if Redis is unavailable
- Metrics and monitoring
-
Connection Pooling: The go-redis client handles connection pooling automatically. Configure pool size based on your load:
client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", PoolSize: 100, MinIdleConns: 10, })
-
Key Naming: Consider prefixing Redis keys to avoid collisions:
cacheKey := fmt.Sprintf("jwks:%s", jwksURI)
You can test with any valid JWT from your configured issuers:
curl -H "Authorization: Bearer <your-jwt-token>" http://localhost:3000The response will show which issuer validated the token:
{
"issuer": "https://tenant1.auth0.com/",
"subject": "auth0|123456",
"claims": { ... }
}