Skip to content

[PoC] TT-17423: return 401 (not 400) on missing JWT Authorization for PRM/MCP flow#8425

Draft
edsonmichaque wants to merge 2 commits into
masterfrom
TT-17423-poc-jwt-missing-auth-401
Draft

[PoC] TT-17423: return 401 (not 400) on missing JWT Authorization for PRM/MCP flow#8425
edsonmichaque wants to merge 2 commits into
masterfrom
TT-17423-poc-jwt-missing-auth-401

Conversation

@edsonmichaque

Copy link
Copy Markdown
Contributor

What

When the Authorization header is missing, the JWT middleware returns 401 Unauthorized instead of 400 Bad Request.

Why

RFC 9728 (Protected Resource Metadata) / MCP clients — e.g. VS Code — only begin the PRM-discovery + OAuth flow when they receive a 401 challenge. Returning 400 on a missing credential (even with the WWW-Authenticate: ... resource_metadata=... header) leaves spec-compliant clients unable to authenticate against a protected MCP server.

Change

gateway/mw_jwt.go — in JWTMiddleware.ProcessRequest, the missing-Authorization-field path now calls prmError(..., http.StatusUnauthorized) instead of http.StatusBadRequest. Single-hunk, +6/-1.

Scope / status

Proof of concept, draft. Gateway-side counterpart of the EDP PRM work under TT-17423 (portal PoC + dcr scope-normalization PR). Not intended to merge as-is — opening for review/alignment. Needs: confirmation that 401 is correct for all missing-credential JWT cases (not just MCP/PRM), and test coverage for the status change.

Testing

  • go build ./gateway/ passes.
  • No automated test added yet (PoC).

… PRM flow

When the Authorization header is absent, the JWT middleware returned 400
Bad Request. RFC 9728 / MCP clients (e.g. VS Code) only begin the
PRM-discovery + OAuth flow on a 401 challenge, so a 400 — even with the
WWW-Authenticate resource_metadata header — leaves spec-compliant clients
unable to authenticate. Return 401 Unauthorized instead.

Gateway-side counterpart of the EDP PRM work (TT-17423; portal + dcr).
@probelabs

probelabs Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

This PR changes the JWT middleware to return a 401 Unauthorized status code instead of 400 Bad Request when the Authorization header is missing. This is to align with RFC 9728 (Protected Resource Metadata) and ensure compatibility with MCP clients like VS Code, which require a 401 challenge to initiate their authentication flow.

The core logic change is a single line in gateway/mw_jwt.go. This PR also updates existing tests in gateway/mw_jwt_test.go to assert the new 401 status code, ensuring the change is validated.

Files Changed Analysis

  • gateway/mw_jwt.go: In the JWTMiddleware.ProcessRequest function, the error path for a missing Authorization field now calls prmError with http.StatusUnauthorized instead of http.StatusBadRequest.
  • gateway/mw_jwt_test.go: The test cases TestJWTSessionFailRSA_EmptyJWT and TestJWTSessionFailRSA_NoAuthHeader are updated to expect a 401 status code, reflecting the new behavior.

Architecture & Impact Assessment

  • What this PR accomplishes: It corrects the gateway's response for missing credentials to comply with RFC 9728, unblocking the authentication flow for spec-compliant PRM/MCP clients.
  • Key technical changes: The HTTP status code for a missing Authorization header in the JWT middleware is changed from 400 to 401.
  • Affected system components: This affects the JWT Authentication Middleware and, by extension, all API endpoints that use it. The change alters a public-facing aspect of the API's error handling.

Authentication Flow

sequenceDiagram
    participant Client as "PRM/MCP Client"
    participant Gateway as "Tyk Gateway (JWT Middleware)"

    alt "Before (master)"
        Client->>Gateway: GET /protected/resource (No Authorization header)
        Gateway-->>Client: 400 Bad Request
        Note right of Client: Client receives unexpected error, cannot start auth flow.

    alt "After (This PR)"
        Client->>Gateway: GET /protected/resource (No Authorization header)
        Gateway-->>Client: 401 Unauthorized (with WWW-Authenticate header)
        Note right of Client: Client receives expected challenge, initiates PRM discovery and OAuth.
    end
end

Loading

Scope Discovery & Context Expansion

  • The change is localized to one error-handling path but has a broad impact on the behavior of all JWT-protected endpoints.
  • The author correctly notes this is a Proof of Concept and raises the question of whether this change is appropriate for all JWT use cases. From an HTTP standards perspective, 401 is the correct status for missing authentication, so this change appears to be a move towards greater compliance.
  • Reviewers should consider the impact on existing clients that may have been coded to expect a 400 response for this specific scenario.
  • The use of the prmError function is notable. Reviewers should verify its implementation ensures the WWW-Authenticate header is correctly formatted for the PRM/MCP flow, as this is a critical part of the client discovery process.
Metadata
  • Review Effort: 2 / 5
  • Primary Label: n/a

Powered by Visor from Probelabs

Last updated: 2026-07-02T15:25:14.064Z | Triggered by: pr_updated | Commit: e86a721

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

@probelabs

probelabs Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

✅ Security Check Passed

No security issues found – changes LGTM.

Architecture Issues (1)

Severity Location Issue
🟡 Warning gateway/mw_jwt.go:1269
Changing the status code for a missing 'Authorization' header from 400 to 401 is a global change for all APIs using JWT middleware. While this aligns with RFC 9728 for PRM/MCP flows and is semantically more correct according to HTTP standards, it could be a breaking change for existing clients that specifically expect a 400 status code for this condition. The change applies to all JWT use cases, not just the one that requires it.
💡 SuggestionTo avoid breaking existing integrations, consider making this behavior configurable within the API definition. A new boolean field, for example `jwt_options.use_401_for_missing_credentials`, could be added. This would allow new use cases like PRM/MCP to opt-in to the 401 response while maintaining backward compatibility for all other existing JWT-protected APIs.

✅ Performance Check Passed

No performance issues found – changes LGTM.

Quality Issues (1)

Severity Location Issue
🟡 Warning gateway/mw_jwt.go:1269
The change from a 400 to a 401 status code for a missing Authorization header is not covered by any tests. As this is a behavioral change in the JWT middleware, it could have wide-ranging effects on clients expecting a 400 status. The PR description acknowledges this as a Proof of Concept, but for merging, tests are essential.
💡 SuggestionAdd a new test case or update an existing one in the JWT middleware test suite to assert that a `401 Unauthorized` status code is returned when the `Authorization` header is missing. This will validate the new behavior and prevent future regressions.

Powered by Visor from Probelabs

Last updated: 2026-07-02T15:24:56.188Z | Triggered by: pr_updated | Commit: e86a721

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

The 400->401 change in mw_jwt.go broke two existing tests that asserted the
old status on the missing-credential path (rawJWT == ""):
- TestJWTSessionFailRSA_NoAuthHeader (no Authorization header)
- TestJWTSessionFailRSA_EmptyJWT (empty Authorization header)
Both hit the same branch; update them to expect http.StatusUnauthorized.
These now cover the PoC change. Both pass.
@sentinelone-cnapp-eu1

Copy link
Copy Markdown

SentinelOne CNS Hardcoded Secret Detector
✅ Congratulations, your code is safe

SentinelOne CNS is a cloud-agnostic, agentless CSPM & CWPP solution that continuously detects and prevents vulnerabilities that have the highest probability of being exploited in Azure, AWS, Google Cloud, and Kubernetes.

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.

1 participant