Skip to content

[TT-16245] Configurable Gateway-default JWKS cache timeout - #7690

Merged
shults merged 2 commits into
masterfrom
TT-16245-configurable-gateway-default-jwks-cache-timeout
Feb 4, 2026
Merged

[TT-16245] Configurable Gateway-default JWKS cache timeout#7690
shults merged 2 commits into
masterfrom
TT-16245-configurable-gateway-default-jwks-cache-timeout

Conversation

@shults

@shults shults commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

Description

Related Issue

Motivation and Context

How This Has Been Tested

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring or add test (improvements in base code or adds test coverage to functionality)

Checklist

  • I ensured that the documentation is up to date
  • I explained why this PR updates go.mod in detail with reasoning why it's required
  • I would like a code coverage CI quality gate exception and have explained why

Ticket Details

TT-16245
Status In Code Review
Summary Configurable Gateway-default JWKS cache timeout

Generated at: 2026-02-04 14:46:02

@github-actions

github-actions Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

API Changes

--- prev.txt	2026-02-04 14:46:55.029053076 +0000
+++ current.txt	2026-02-04 14:46:44.330030913 +0000
@@ -6690,6 +6690,9 @@
 	Streaming StreamingConfig `json:"streaming"`
 
 	Labs LabsConfig `json:"labs"`
+
+	// JWKS holds the configuration for Tyk JWKS functionalities
+	JWKS JWKSConfig `json:"jwks"`
 }
     Config is the configuration object used by Tyk to set up various parameters.
 
@@ -7004,6 +7007,17 @@
 
 type IPsHandleStrategy string
 
+type JWKSCacheConfig struct {
+	// Timeout defines how long the JWKS will be kept in the cache before forcing a refresh from the JWKS endpoint.
+	// Default is 240 seconds (4 minutes). Set to 0 to use the default value.
+	Timeout int64 `json:"timeout"`
+}
+
+type JWKSConfig struct {
+	// Cache hodls configuration for JWKS caching
+	Cache JWKSCacheConfig `json:"cache"`
+}
+
 type LabsConfig map[string]interface{}
     LabsConfig include config for streaming
 
@@ -9277,7 +9291,6 @@
     GatewayFireSystemEvent declared as global variable, set during gw start
 
 var GetJWK = getJWK
-var JWKCaches = sync.Map{}
 var LoopHostRE = regexp.MustCompile("tyk://([^/]+)")
 var NonAlphaNumRE = regexp.MustCompile("[^A-Za-z0-9]+")
 var TykErrors = make(map[string]config.TykError)

@probelabs

probelabs Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces a configurable timeout for the gateway-default JSON Web Key Set (JWKS) cache, allowing operators to set a custom duration via the jwks.cache.timeout configuration setting.

The more significant change in this PR is a substantial architectural refactoring of the JWKS caching mechanism. The implementation moves away from using global, package-level variables for caching and centralizes this state within the Gateway struct. This shift from global to instance-based state management enhances encapsulation, improves testability by isolating state, and reduces the risk of race conditions in a concurrent environment.

Files Changed Analysis

The changes are spread across configuration, core gateway logic, authentication middlewares, and their corresponding tests.

  • Configuration (config/config.go, cli/linter/schema.json): Introduces JWKSConfig and JWKSCacheConfig structs to handle the new jwks.cache.timeout setting and updates the linter schema for validation.
  • Gateway Server (gateway/server.go): This file is central to the refactoring. The global cache variables are removed and replaced by jwkCache and apiJWKCaches fields within the Gateway struct. A new buildJWKSCache function is added to initialize the cache based on the new configuration.
  • Middlewares (gateway/mw_jwt.go, gateway/mw_external_oauth.go): Both JWT and External OAuth middlewares are updated to access the JWKS cache via the Gateway instance (k.Gw) instead of the former global variables. Cache management functions have been converted to methods on the Gateway struct.
  • Distributed Cache Invalidation (gateway/redis_signals.go, gateway/rpc_storage_handler.go): The logic for invalidating caches via Redis Pub/Sub and RPC is updated to call methods on the Gateway instance, ensuring the refactored instance-based caches are correctly flushed in a clustered setup.
  • Cache Implementation (internal/cache/repository.go): The cache repository is slightly modified to better support the new structure and testing.
  • Tests (*_test.go): All related tests are refactored to work with the new instance-based cache on the Gateway object, removing dependencies on global state.

Architecture & Impact Assessment

  • What this PR accomplishes:

    1. Configurability: Introduces a user-configurable cache timeout for JWKS, providing more control over key fetching behavior.
    2. Architectural Improvement: Refactors JWKS caching from a problematic global state to a more robust instance-level state, improving code encapsulation, concurrency safety, and testability.
  • Key technical changes introduced:

    • A new configuration path jwks.cache.timeout is added to tyk.conf.
    • Global variables for caching (JWKCaches, externalOAuthJWKCache) are eliminated.
    • Cache management logic and state are now encapsulated within the Gateway struct.
    • Functions for creating and invalidating caches are now methods on the Gateway struct.
  • Affected system components:

    • Configuration: A new setting is available for operators.
    • Gateway Initialization: The server now initializes and manages the JWKS cache as part of the Gateway instance's lifecycle.
    • Authentication Middlewares: The JWT and External OAuth middlewares now depend on the Gateway instance for all caching operations.
    • Cache Invalidation: The distributed cache invalidation mechanism is updated to correctly interact with the Gateway instance.
  • Architectural Shift: From Global to Instance-Based Caching

graph TD
    subgraph "Before: Global State"
        G_MW_JWT["JWT Middleware"] --> G_JWKCaches["Global JWKCaches (sync.Map)"]
        G_MW_OAuth["External OAuth Middleware"] --> G_OAuthCache["Global externalOAuthJWKCache"]
    end

    subgraph "After: Instance-based State"
        subgraph GatewayInstance["Gateway Instance"]
            direction LR
            apiJWKCaches["apiJWKCaches (sync.Map)"]
            jwkCache["jwkCache"]
        end
        MW_JWT["JWT Middleware"] --> |accesses k.Gw.apiJWKCaches| GatewayInstance
        MW_OAuth["External OAuth Middleware"] --> |accesses k.Gw.jwkCache| GatewayInstance
    end
Loading

Scope Discovery & Context Expansion

The scope of this PR extends significantly beyond adding a simple configuration option. The refactoring of state management from global to instance-based is a foundational architectural change that positively impacts the entire lifecycle of requests involving JWT validation.

  • Broader Impact:
    • Gateway Startup: The gateway now parses the jwks.cache.timeout setting and initializes the cache instances with the specified (or default) timeout.
    • Middleware Execution: Every request using JWT or External OAuth authentication will now interact with the Gateway instance's cache, respecting the configured timeout.
    • Distributed Systems: The cache invalidation mechanism, which is critical for consistency in a multi-gateway cluster, is updated to correctly target the new instance-based caches. This ensures that cache flushes initiated via API calls, Redis events, or RPC propagate correctly across the cluster.

This move away from global variables represents a significant improvement in the gateway's robustness, making its internal state management more predictable, testable, and easier to reason about.

Metadata
  • Review Effort: 3 / 5
  • Primary Label: feature

Powered by Visor from Probelabs

Last updated: 2026-02-04T14:51:12.165Z | Triggered by: pr_updated | Commit: f85bdde

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs

probelabs Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

Security Issues (2)

Severity Location Issue
🟡 Warning gateway/server.go:2327-2329
The configurable JWKS cache timeout (`jwks.cache.timeout`) lacks a sane minimum value enforcement. A very low, non-zero timeout (e.g., 1 second) can lead to excessive requests to the JWKS endpoint, potentially causing a denial-of-service condition for the gateway or the identity provider.
💡 SuggestionEnforce a reasonable minimum timeout value. For example, if the configured timeout is greater than 0 but less than a safe threshold (e.g., 60 seconds), either clamp it to the threshold or log a prominent warning on startup.
🟡 Warning gateway/server.go:2327-2329
The configurable JWKS cache timeout does not have a maximum value. Setting an excessively long timeout can delay the discovery of revoked or rotated keys from the JWKS endpoint, extending the validity window for tokens signed with compromised keys. This undermines the effectiveness of key rotation policies.
💡 SuggestionConsider logging a warning on startup if the configured value exceeds a recommended threshold (e.g., 24 hours), or enforcing a hard maximum. Ensure documentation clearly explains the security implications of setting a long timeout.

Architecture Issues (1)

Severity Location Issue
🟢 Info internal/cache/repository.go:23
The `New` function's signature has been changed to return a concrete type `*MemRepository` instead of the `Repository` interface. While this facilitates access to implementation-specific methods for testing, it couples consumers of the `cache.New` function to a specific implementation, which is generally discouraged in favor of returning interfaces to promote abstraction and reduce coupling.
💡 SuggestionConsider reverting the signature of `New` to return the `Repository` interface to maintain loose coupling. The test that requires access to the concrete type's methods (`Test_buildJWKSCache`) can use a type assertion to safely access the `*MemRepository` instance and its methods. This approach preserves a cleaner public API for the package.

Example of using type assertion in the test:

cacheInstance := buildJWKSCache(tt.cfg)
memCache, ok := cacheInstance.(*cache.MemRepository)
require.True(t, ok, &#34;buildJWKSCache should return a *cache.MemRepository&#34;)
assert.Equal(t, tt.expectedTimeout, memCache.DefaultExpiration())

✅ Performance Check Passed

No performance issues found – changes LGTM.

Quality Issues (3)

Severity Location Issue
🟢 Info config/config.go:726
There is a typo in the comment for the `Cache` field. 'hodls' should be 'holds'.
💡 SuggestionCorrect the typo in the comment from 'hodls' to 'holds' to improve code readability and maintainability.
🟡 Warning gateway/mw_jwt.go:1633-1671
The `Gateway` methods `loadOrCreateJWKCacheByApiID` and `deleteJWKCacheByAPIID` are defined in `gateway/mw_jwt.go`. To improve code organization and adhere to the principle of separation of concerns, these methods should be moved to `gateway/server.go`, where the `Gateway` struct is defined.
💡 SuggestionMove the `loadOrCreateJWKCacheByApiID` and `deleteJWKCacheByAPIID` methods from `gateway/mw_jwt.go` to `gateway/server.go` to co-locate them with the `Gateway` struct definition.
🟡 Warning internal/cache/repository.go:82-89
The `DefaultExpiration()` and `CleanupInterval()` methods were added to `MemRepository` but not to the `Repository` interface. This forces consumers that need these values, such as tests, to perform a type assertion to the concrete `*cache.MemRepository` type, which breaks the abstraction provided by the interface.
💡 SuggestionTo maintain a clean abstraction, consider adding `DefaultExpiration()` and `CleanupInterval()` to the `Repository` interface if they represent a core part of the cache's contract. Alternatively, refactor the tests to not depend on these implementation-specific details.

Powered by Visor from Probelabs

Last updated: 2026-02-04T14:51:15.674Z | Triggered by: pr_updated | Commit: f85bdde

💡 TIP: You can chat with Visor using /visor ask <your question>

@shults
shults force-pushed the TT-16245-configurable-gateway-default-jwks-cache-timeout branch 3 times, most recently from e5a9fb5 to 0db45b2 Compare January 26, 2026 09:01
Comment thread internal/cache/repository.go Outdated
@shults
shults force-pushed the TT-16245-configurable-gateway-default-jwks-cache-timeout branch 2 times, most recently from f51a2c0 to 8bdf12d Compare February 4, 2026 09:05
@shults
shults force-pushed the TT-16245-configurable-gateway-default-jwks-cache-timeout branch from 8bdf12d to f85bdde Compare February 4, 2026 14:45

@edsonmichaque edsonmichaque left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@sonarqubecloud

sonarqubecloud Bot commented Feb 4, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
95.6% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@shults
shults enabled auto-merge (squash) February 4, 2026 16:47
@shults
shults merged commit d498a3f into master Feb 4, 2026
39 of 51 checks passed
@shults
shults deleted the TT-16245-configurable-gateway-default-jwks-cache-timeout branch February 4, 2026 16:47
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.

3 participants