Skip to content

Commit a215fa7

Browse files
author
Jihaochang
committed
fix(auth): send auth challenge without error when no token is present
Per RFC 6750 §3.1, when a request lacks any authentication, the resource server SHOULD NOT include an error code. The current code returns error="invalid_token" for both missing and invalid tokens, which prevents clients from distinguishing between the two cases. This change: - Detects whether an Authorization header was present - For missing credentials: returns challenge with resource_metadata and scope (no error code) - For invalid credentials: keeps the existing invalid_token response Fixes #3769
1 parent d7845c0 commit a215fa7

1 file changed

Lines changed: 49 additions & 3 deletions

File tree

‎src/mcp/server/auth/middleware/bearer_auth.py‎

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,20 @@ def __init__(
115115
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
116116
auth_user = scope.get("user")
117117
if not isinstance(auth_user, AuthenticatedUser):
118-
await self._send_auth_error(
119-
send, status_code=401, error="invalid_token", description="Authentication required"
120-
)
118+
# Check if the request had an Authorization header at all
119+
headers = scope.get("headers", []) or []
120+
had_auth = any(h == b"authorization" for h, _ in headers)
121+
122+
if had_auth:
123+
# Invalid token - send error="invalid_token"
124+
await self._send_auth_error(
125+
send, status_code=401, error="invalid_token", description="Authentication required"
126+
)
127+
else:
128+
# No auth header - send challenge without error code per RFC 6750
129+
await self._send_auth_challenge(
130+
send, status_code=401
131+
)
121132
return
122133

123134
auth_credentials = scope.get("auth")
@@ -163,3 +174,38 @@ async def _send_auth_error(self, send: Send, status_code: int, error: str, descr
163174
"body": body_bytes,
164175
}
165176
)
177+
178+
async def _send_auth_challenge(self, send: Send, status_code: int) -> None:
179+
"""Send an authentication challenge without error code (per RFC 6750 §3.1).
180+
181+
When no credentials are presented, the server SHOULD NOT include an error
182+
code. Instead, it should return the resource metadata and required scopes
183+
so the client can perform its own scope selection.
184+
"""
185+
www_auth_parts = []
186+
if self.resource_metadata_url:
187+
www_auth_parts.append(f'resource_metadata="{self.resource_metadata_url}"')
188+
if self.required_scopes:
189+
www_auth_parts.append(f'scope="{" ".join(self.required_scopes)}"')
190+
191+
www_authenticate = f"Bearer {', '.join(www_auth_parts)}" if www_auth_parts else "Bearer"
192+
193+
body = b""
194+
await send(
195+
{
196+
"type": "http.response.start",
197+
"status": status_code,
198+
"headers": [
199+
(b"content-type", b"application/json"),
200+
(b"content-length", b"0"),
201+
(b"www-authenticate", www_authenticate.encode()),
202+
],
203+
}
204+
)
205+
206+
await send(
207+
{
208+
"type": "http.response.body",
209+
"body": body,
210+
}
211+
)

0 commit comments

Comments
 (0)