Skip to content

fix: disable token-based auth for dynamic mTLS APIs by default#7671

Open
buger wants to merge 2 commits intomasterfrom
fix/disable-dynamic-mtls-token-auth
Open

fix: disable token-based auth for dynamic mTLS APIs by default#7671
buger wants to merge 2 commits intomasterfrom
fix/disable-dynamic-mtls-token-auth

Conversation

@buger
Copy link
Member

@buger buger commented Jan 15, 2026

Summary

  • Add new gateway-level config flag security.allow_dynamic_mtls_token_auth (default: false)
  • When disabled, APIs with dynamic mTLS must authenticate via client certificate only
  • Token-only authentication is rejected with "Client certificate required for this API" error

Problem

APIs with dynamic mTLS (use_certificate=true) could be accessed by constructing authentication tokens from:

  • Organization ID (publicly known)
  • SHA256 hash of a valid certificate (publicly available from certificate)

This allowed attackers to bypass mTLS by constructing valid tokens without possessing the actual private key.

Solution

By default, the gateway now requires a valid client certificate to be presented during the TLS handshake for dynamic mTLS APIs. Token-only authentication is rejected.

For backward compatibility, operators can set security.allow_dynamic_mtls_token_auth: true to restore the legacy behavior.

Test plan

  • Verify request with token-only to dynamic mTLS API is rejected (default behavior)
  • Verify request with valid certificate to dynamic mTLS API succeeds (default behavior)
  • Verify request with token-only to dynamic mTLS API succeeds when flag is enabled
  • Verify non-mTLS APIs are unaffected by this change

🤖 Generated with Claude Code

Add a new gateway-level configuration flag `security.allow_dynamic_mtls_token_auth`
(default: false) that controls whether token-based authentication is allowed for
APIs using dynamic client mTLS.

Previously, APIs with dynamic mTLS enabled would accept authentication via either
a client certificate OR a token. The token is constructed from the organization ID
and SHA256 hash of the certificate - information that is publicly available. This
allowed attackers to bypass mTLS by constructing tokens without possessing the
actual private key.

With this change:
- Default behavior: Token-only authentication is rejected for dynamic mTLS APIs.
  Clients must present a valid certificate during the TLS handshake.
- Legacy behavior: Set `security.allow_dynamic_mtls_token_auth: true` in gateway
  config to restore the previous behavior of allowing either certificate or token.

This is a security enhancement that enforces the principle that mTLS should require
possession of the private key, not just knowledge of the certificate hash.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@probelabs
Copy link

probelabs bot commented Jan 15, 2026

This PR addresses a security vulnerability by disabling token-based authentication for dynamic mTLS-enabled APIs by default, requiring clients to present a valid certificate instead.

Files Changed Analysis

  • config/config.go: Introduces a new boolean configuration field, AllowDynamicMTLSTokenAuth, within the SecurityConfig struct. This flag defaults to false, enforcing the new, more secure behavior. Setting it to true restores the previous, less secure behavior for backward compatibility.
  • gateway/mw_auth_key.go: The core logic change resides here. The authentication middleware now checks if an API has dynamic mTLS enabled (UseCertificate: true). If so, and if the new config flag is false, it rejects requests that use token-based authentication without a corresponding client certificate presented during the TLS handshake. A new error, ErrAuthCertRequired, is introduced for this case.

Architecture & Impact Assessment

  • What this PR accomplishes: It mitigates a security risk where an attacker could bypass mTLS for dynamic mTLS APIs. Previously, one could forge a valid authentication token using publicly available information (organization ID and a certificate's SHA256 hash) without possessing the certificate's private key. This change enforces that the client must prove possession of the private key by presenting the certificate during the TLS handshake.
  • Key technical changes introduced:
    1. A new gateway-level configuration flag, security.allow_dynamic_mtls_token_auth, is added, defaulting to false.
    2. The authentication middleware is updated to enforce client certificate presentation for APIs with use_certificate: true.
    3. A new error ErrAuthCertRequired with the message "Client certificate required for this API" is returned for failed requests.
  • Affected system components: The primary impact is on the Gateway's Authentication Middleware. Specifically, it affects API definitions that have the use_certificate flag set to true in their auth_configs.

Request Flow Visualization

sequenceDiagram
    participant Client
    participant TykGateway as Tyk Gateway
    participant Upstream

    alt Request with Auth Token only (Default Behavior)
        Client->>+TykGateway: Request with Auth Token
        Note over TykGateway: API has `use_certificate: true`
        Note over TykGateway: `allow_dynamic_mtls_token_auth` is false
        TykGateway-->>Client: 403 Forbidden ("Client certificate required")
        TykGateway--xUpstream: Request Blocked
    end

    alt Request with Client Certificate
        Client->>+TykGateway: Request with Client Certificate
        Note over TykGateway: API has `use_certificate: true`
        TykGateway->>TykGateway: Validate Certificate & Authenticate
        TykGateway->>+Upstream: Forward Request
        Upstream-->>-TykGateway: Response
        TykGateway-->>-Client: Response
    end
Loading

Scope Discovery & Context Expansion

  • The change is narrowly scoped to the authentication key middleware (gateway/mw_auth_key.go). Its activation depends on two conditions: the API-level setting use_certificate: true in the apidef.AuthConfig struct (defined in apidef/api_definitions.go) and the new gateway-level setting security.allow_dynamic_mtls_token_auth: false.
  • The PR description outlines a clear test plan, but the implementation of these tests is missing from the current changes. To ensure correctness and prevent regressions, automated tests should be added to gateway/mw_auth_key_test.go to cover:
    1. Rejection of token-only requests to a dynamic mTLS API (default behavior).
    2. Successful authentication for requests with a valid client certificate to a dynamic mTLS API.
    3. Successful authentication for token-only requests when allow_dynamic_mtls_token_auth is explicitly set to true.
    4. Confirmation that non-mTLS APIs are unaffected by this change.
Metadata
  • Review Effort: 2 / 5
  • Primary Label: bug

Powered by Visor from Probelabs

Last updated: 2026-01-15T11:15:18.948Z | Triggered by: pr_updated | Commit: 53b84c0

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 2026

API Changes

--- prev.txt	2026-01-15 11:14:57.879505110 +0000
+++ current.txt	2026-01-15 11:14:48.110500983 +0000
@@ -7181,6 +7181,14 @@
 
 	// CertificateExpiryMonitor configures the certificate expiry monitoring and notification feature
 	CertificateExpiryMonitor CertificateExpiryMonitorConfig `json:"certificate_expiry_monitor"`
+
+	// AllowDynamicMTLSTokenAuth controls whether token-based authentication is allowed for APIs
+	// using dynamic client mTLS. When set to false (the default), requests to APIs with dynamic
+	// mTLS enabled must present a valid client certificate - token-only authentication will be
+	// rejected. This prevents bypassing mTLS by constructing tokens from publicly available
+	// certificate information. Set to true to restore the legacy behavior of allowing either
+	// certificate or token authentication.
+	AllowDynamicMTLSTokenAuth bool `json:"allow_dynamic_mtls_token_auth"`
 }
 
 type ServiceConfig struct {
@@ -8981,10 +8989,12 @@
 	ErrAuthCertNotFound              = "auth.cert_not_found"
 	ErrAuthCertExpired               = "auth.cert_expired"
 	ErrAuthKeyIsInvalid              = "auth.key_is_invalid"
+	ErrAuthCertRequired              = "auth.cert_required"
 
-	MsgNonExistentKey  = "Attempted access with non-existent key."
-	MsgNonExistentCert = "Attempted access with non-existent cert."
-	MsgInvalidKey      = "Attempted access with invalid key."
+	MsgNonExistentKey      = "Attempted access with non-existent key."
+	MsgNonExistentCert     = "Attempted access with non-existent cert."
+	MsgInvalidKey          = "Attempted access with invalid key."
+	MsgCertificateRequired = "Client certificate required for this API."
 )
 const (
 	KID       = "kid"

@probelabs
Copy link

probelabs bot commented Jan 15, 2026

Security Issues (1)

Severity Location Issue
🟡 Warning gateway/mw_auth_key.go:130-138
The security fix that prevents an authentication bypass for dynamic mTLS APIs is not covered by automated tests. While the logic appears correct, the lack of tests makes it vulnerable to future regressions which could reintroduce the security flaw.
💡 SuggestionImplement the test cases outlined in the pull request description to ensure the fix is robust and prevent regressions. This includes verifying that token-only requests are rejected by default, certificate-based requests succeed, the backward-compatibility flag works as expected, and non-mTLS APIs are unaffected.

Architecture Issues (1)

Severity Location Issue
🟠 Error gateway/mw_auth_key.go:130-140
The new security logic to prevent token-based authentication bypass for dynamic mTLS APIs is not covered by automated tests. For a security-sensitive change, it is critical to have tests that validate the new behavior (rejecting token-only auth), the backward-compatibility flag, and ensure no regressions for non-mTLS APIs or certificate-based mTLS auth.
💡 SuggestionAdd unit tests to `gateway/mw_auth_key_test.go` covering the scenarios outlined in the PR description's test plan: 1. Request with token-only to dynamic mTLS API is rejected (default). 2. Request with valid certificate to dynamic mTLS API succeeds (default). 3. Request with token-only to dynamic mTLS API succeeds when `AllowDynamicMTLSTokenAuth` is true. 4. Non-mTLS APIs are unaffected.

✅ Performance Check Passed

No performance issues found – changes LGTM.

Quality Issues (1)

Severity Location Issue
🟡 Warning gateway/mw_auth_key.go:130-146
The new security-critical logic to reject token-based authentication for dynamic mTLS APIs is not covered by automated tests. The PR description includes a comprehensive test plan, but the corresponding tests have not been implemented. Without automated tests, there is a risk of future regressions and it's difficult to verify the correctness of all edge cases.
💡 SuggestionImplement the test cases outlined in the PR description to ensure the new logic is fully validated. This should include tests for: 1. Rejection of token-only requests to mTLS APIs by default. 2. Successful authentication with a valid client certificate. 3. Successful token-only authentication when `AllowDynamicMTLSTokenAuth` is explicitly enabled for backward compatibility. 4. Verification that non-mTLS APIs are unaffected by this change.

Powered by Visor from Probelabs

Last updated: 2026-01-15T11:15:24.124Z | Triggered by: pr_updated | Commit: 53b84c0

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

@github-actions
Copy link
Contributor

🚨 Jira Linter Failed

Commit: 53b84c0
Failed at: 2026-01-15 11:14:21 UTC

The Jira linter failed to validate your PR. Please check the error details below:

🔍 Click to view error details
failed to validate branch and PR title rules: branch name 'fix/disable-dynamic-mtls-token-auth' must contain a valid Jira ticket ID (e.g., ABC-123)

Next Steps

  • Ensure your branch name contains a valid Jira ticket ID (e.g., ABC-123)
  • Verify your PR title matches the branch's Jira ticket ID
  • Check that the Jira ticket exists and is accessible

This comment will be automatically deleted once the linter passes.

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