Skip to content

Commit 90005e4

Browse files
committed
refactor
1 parent 8862507 commit 90005e4

File tree

6 files changed

+90
-54
lines changed

6 files changed

+90
-54
lines changed

examples/servers/simple-auth/mcp_simple_auth/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from mcp.server.auth.middleware.auth_context import get_access_token
2020
from mcp.server.auth.settings import AuthSettings
21-
from mcp.server.auth.token_verifier import IntrospectionTokenVerifier
21+
from .token_verifier import IntrospectionTokenVerifier
2222
from mcp.server.fastmcp.server import FastMCP
2323

2424
logger = logging.getLogger(__name__)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Example token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
2+
3+
import logging
4+
from mcp.server.auth.provider import AccessToken
5+
6+
logger = logging.getLogger(__name__)
7+
8+
9+
class IntrospectionTokenVerifier:
10+
"""Example token verifier that uses OAuth 2.0 Token Introspection (RFC 7662).
11+
12+
This is a simple example implementation for demonstration purposes.
13+
Production implementations should consider:
14+
- Connection pooling and reuse
15+
- More sophisticated error handling
16+
- Rate limiting and retry logic
17+
- Comprehensive configuration options
18+
"""
19+
20+
def __init__(self, introspection_endpoint: str):
21+
self.introspection_endpoint = introspection_endpoint
22+
23+
async def verify_token(self, token: str) -> AccessToken | None:
24+
"""Verify token via introspection endpoint."""
25+
import httpx
26+
27+
# Validate URL to prevent SSRF attacks
28+
if not self.introspection_endpoint.startswith(('https://', 'http://localhost', 'http://127.0.0.1')):
29+
logger.warning(f"Rejecting introspection endpoint with unsafe scheme: {self.introspection_endpoint}")
30+
return None
31+
32+
# Configure secure HTTP client
33+
timeout = httpx.Timeout(10.0, connect=5.0)
34+
limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
35+
36+
async with httpx.AsyncClient(
37+
timeout=timeout,
38+
limits=limits,
39+
verify=True, # Enforce SSL verification
40+
) as client:
41+
try:
42+
response = await client.post(
43+
self.introspection_endpoint,
44+
data={"token": token},
45+
headers={"Content-Type": "application/x-www-form-urlencoded"},
46+
)
47+
48+
if response.status_code != 200:
49+
logger.debug(f"Token introspection returned status {response.status_code}")
50+
return None
51+
52+
data = response.json()
53+
if not data.get("active", False):
54+
return None
55+
56+
return AccessToken(
57+
token=token,
58+
client_id=data.get("client_id", "unknown"),
59+
scopes=data.get("scope", "").split() if data.get("scope") else [],
60+
expires_at=data.get("exp"),
61+
)
62+
except Exception as e:
63+
logger.warning(f"Token introspection failed: {e}")
64+
return None

src/mcp/server/auth/provider.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,19 @@ def construct_redirect_uri(redirect_uri_base: str, **params: str | None) -> str:
278278

279279
redirect_uri = urlunparse(parsed_uri._replace(query=urlencode(query_params)))
280280
return redirect_uri
281+
282+
283+
class ProviderTokenVerifier:
284+
"""Token verifier that uses an OAuthAuthorizationServerProvider.
285+
286+
This is provided for backwards compatibility with existing auth_server_provider
287+
configurations. For new implementations using AS/RS separation, consider using
288+
the TokenVerifier protocol with a dedicated implementation like IntrospectionTokenVerifier.
289+
"""
290+
291+
def __init__(self, provider: "OAuthAuthorizationServerProvider[AccessToken, RefreshToken, AuthorizationCode]"):
292+
self.provider = provider
293+
294+
async def verify_token(self, token: str) -> AccessToken | None:
295+
"""Verify token using the provider's load_access_token method."""
296+
return await self.provider.load_access_token(token)
Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
"""Token verification protocol and implementations."""
1+
"""Token verification protocol."""
22

3-
from typing import Any, Protocol, runtime_checkable
3+
from typing import Protocol, runtime_checkable
44

5-
from mcp.server.auth.provider import AccessToken, OAuthAuthorizationServerProvider
5+
from mcp.server.auth.provider import AccessToken
66

77

88
@runtime_checkable
@@ -12,49 +12,3 @@ class TokenVerifier(Protocol):
1212
async def verify_token(self, token: str) -> AccessToken | None:
1313
"""Verify a bearer token and return access info if valid."""
1414
...
15-
16-
17-
class ProviderTokenVerifier:
18-
"""Token verifier that uses an OAuthAuthorizationServerProvider."""
19-
20-
def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]):
21-
self.provider = provider
22-
23-
async def verify_token(self, token: str) -> AccessToken | None:
24-
"""Verify token using the provider's load_access_token method."""
25-
return await self.provider.load_access_token(token)
26-
27-
28-
class IntrospectionTokenVerifier:
29-
"""Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662)."""
30-
31-
def __init__(self, introspection_endpoint: str):
32-
self.introspection_endpoint = introspection_endpoint
33-
34-
async def verify_token(self, token: str) -> AccessToken | None:
35-
"""Verify token via introspection endpoint."""
36-
import httpx
37-
38-
async with httpx.AsyncClient() as client:
39-
try:
40-
response = await client.post(
41-
self.introspection_endpoint,
42-
data={"token": token},
43-
headers={"Content-Type": "application/x-www-form-urlencoded"},
44-
)
45-
46-
if response.status_code != 200:
47-
return None
48-
49-
data = response.json()
50-
if not data.get("active", False):
51-
return None
52-
53-
return AccessToken(
54-
token=token,
55-
client_id=data.get("client_id", "unknown"),
56-
scopes=data.get("scope", "").split() if data.get("scope") else [],
57-
expires_at=data.get("exp"),
58-
)
59-
except Exception:
60-
return None

src/mcp/server/fastmcp/server.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
BearerAuthBackend,
3131
RequireAuthMiddleware,
3232
)
33-
from mcp.server.auth.provider import OAuthAuthorizationServerProvider
33+
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier
3434
from mcp.server.auth.settings import AuthSettings
35-
from mcp.server.auth.token_verifier import ProviderTokenVerifier, TokenVerifier
35+
from mcp.server.auth.token_verifier import TokenVerifier
3636
from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, elicit_with_validation
3737
from mcp.server.fastmcp.exceptions import ResourceError
3838
from mcp.server.fastmcp.prompts import Prompt, PromptManager
@@ -67,6 +67,8 @@
6767
logger = get_logger(__name__)
6868

6969

70+
71+
7072
class Settings(BaseSettings, Generic[LifespanResultT]):
7173
"""FastMCP server settings.
7274
@@ -169,7 +171,7 @@ def __init__(
169171
self._auth_server_provider = auth_server_provider
170172
self._token_verifier = token_verifier
171173

172-
# Create token verifier from provider if needed
174+
# Create token verifier from provider if needed (backwards compatibility)
173175
if auth_server_provider and not token_verifier:
174176
self._token_verifier = ProviderTokenVerifier(auth_server_provider)
175177
self._event_store = event_store

tests/server/auth/middleware/test_bearer_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
from mcp.server.auth.provider import (
2020
AccessToken,
2121
OAuthAuthorizationServerProvider,
22+
ProviderTokenVerifier,
2223
)
23-
from mcp.server.auth.token_verifier import ProviderTokenVerifier
2424

2525

2626
class MockOAuthProvider:

0 commit comments

Comments
 (0)