Panos Argyrakis Jeremiah Lowin commited on
Commit
60b71f5
·
unverified ·
1 Parent(s): 60e47cc

fixes #1398: Add JWT claims to AccessToken (#1399)

Browse files

Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>

src/fastmcp/server/auth/auth.py CHANGED
@@ -1,7 +1,11 @@
1
  from __future__ import annotations
2
 
 
 
 
 
 
3
  from mcp.server.auth.provider import (
4
- AccessToken,
5
  AuthorizationCode,
6
  OAuthAuthorizationServerProvider,
7
  RefreshToken,
@@ -21,6 +25,12 @@ from pydantic import AnyHttpUrl
21
  from starlette.routing import Route
22
 
23
 
 
 
 
 
 
 
24
  class AuthProvider(TokenVerifierProtocol):
25
  """Base class for all FastMCP authentication providers.
26
 
 
1
  from __future__ import annotations
2
 
3
+ from typing import Any
4
+
5
+ from mcp.server.auth.provider import (
6
+ AccessToken as _SDKAccessToken,
7
+ )
8
  from mcp.server.auth.provider import (
 
9
  AuthorizationCode,
10
  OAuthAuthorizationServerProvider,
11
  RefreshToken,
 
25
  from starlette.routing import Route
26
 
27
 
28
+ class AccessToken(_SDKAccessToken):
29
+ """AccessToken that includes all JWT claims."""
30
+
31
+ claims: dict[str, Any] = {}
32
+
33
+
34
  class AuthProvider(TokenVerifierProtocol):
35
  """Base class for all FastMCP authentication providers.
36
 
src/fastmcp/server/auth/providers/jwt.py CHANGED
@@ -11,12 +11,12 @@ from authlib.jose import JsonWebKey, JsonWebToken
11
  from authlib.jose.errors import JoseError
12
  from cryptography.hazmat.primitives import serialization
13
  from cryptography.hazmat.primitives.asymmetric import rsa
14
- from mcp.server.auth.provider import AccessToken
15
  from pydantic import AnyHttpUrl, SecretStr
16
  from pydantic_settings import BaseSettings, SettingsConfigDict
17
  from typing_extensions import TypedDict
18
 
19
  from fastmcp.server.auth import TokenVerifier
 
20
  from fastmcp.server.auth.registry import register_provider
21
  from fastmcp.utilities.logging import get_logger
22
  from fastmcp.utilities.types import NotSet, NotSetT
@@ -448,6 +448,7 @@ class JWTVerifier(TokenVerifier):
448
  client_id=str(client_id),
449
  scopes=scopes,
450
  expires_at=int(exp) if exp else None,
 
451
  )
452
 
453
  except JoseError:
@@ -535,4 +536,5 @@ class StaticTokenVerifier(TokenVerifier):
535
  client_id=token_data["client_id"],
536
  scopes=scopes,
537
  expires_at=expires_at,
 
538
  )
 
11
  from authlib.jose.errors import JoseError
12
  from cryptography.hazmat.primitives import serialization
13
  from cryptography.hazmat.primitives.asymmetric import rsa
 
14
  from pydantic import AnyHttpUrl, SecretStr
15
  from pydantic_settings import BaseSettings, SettingsConfigDict
16
  from typing_extensions import TypedDict
17
 
18
  from fastmcp.server.auth import TokenVerifier
19
+ from fastmcp.server.auth.auth import AccessToken
20
  from fastmcp.server.auth.registry import register_provider
21
  from fastmcp.utilities.logging import get_logger
22
  from fastmcp.utilities.types import NotSet, NotSetT
 
448
  client_id=str(client_id),
449
  scopes=scopes,
450
  expires_at=int(exp) if exp else None,
451
+ claims=claims,
452
  )
453
 
454
  except JoseError:
 
536
  client_id=token_data["client_id"],
537
  scopes=scopes,
538
  expires_at=expires_at,
539
+ claims=token_data,
540
  )
src/fastmcp/server/dependencies.py CHANGED
@@ -2,10 +2,13 @@ from __future__ import annotations
2
 
3
  from typing import TYPE_CHECKING, ParamSpec, TypeVar
4
 
5
- from mcp.server.auth.middleware.auth_context import get_access_token
6
- from mcp.server.auth.provider import AccessToken
 
7
  from starlette.requests import Request
8
 
 
 
9
  if TYPE_CHECKING:
10
  from fastmcp.server.context import Context
11
 
@@ -94,3 +97,30 @@ def get_http_headers(include_all: bool = False) -> dict[str, str]:
94
  return headers
95
  except RuntimeError:
96
  return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  from typing import TYPE_CHECKING, ParamSpec, TypeVar
4
 
5
+ from mcp.server.auth.middleware.auth_context import (
6
+ get_access_token as _sdk_get_access_token,
7
+ )
8
  from starlette.requests import Request
9
 
10
+ from fastmcp.server.auth.auth import AccessToken
11
+
12
  if TYPE_CHECKING:
13
  from fastmcp.server.context import Context
14
 
 
97
  return headers
98
  except RuntimeError:
99
  return {}
100
+
101
+
102
+ # --- Access Token ---
103
+
104
+
105
+ def get_access_token() -> AccessToken | None:
106
+ """
107
+ Get the FastMCP access token from the current context.
108
+
109
+ Returns:
110
+ The access token if an authenticated user is available, None otherwise.
111
+ """
112
+ #
113
+ obj = _sdk_get_access_token()
114
+ if obj is None or isinstance(obj, AccessToken):
115
+ return obj
116
+
117
+ # If the object is not a FastMCP AccessToken, convert it to one if the fields are compatible
118
+ # This is a workaround for the case where the SDK returns a different type
119
+ # If it fails, it will raise a TypeError
120
+ try:
121
+ return AccessToken(**obj.model_dump())
122
+ except Exception as e:
123
+ raise TypeError(
124
+ f"Expected fastmcp.server.auth.auth.AccessToken, got {type(obj).__name__}. "
125
+ "Ensure the SDK is using the correct AccessToken type."
126
+ ) from e
tests/server/auth/test_jwt_provider.py CHANGED
@@ -141,15 +141,25 @@ class TestBearerTokenJWKS:
141
  url="https://test.example.com/.well-known/jwks.json",
142
  json=mock_jwks_data,
143
  )
 
 
 
 
 
144
  token = rsa_key_pair.create_token(
145
- subject="test-user",
146
- issuer="https://test.example.com",
147
- audience="https://api.example.com",
148
  )
149
 
150
  access_token = await jwks_provider.load_access_token(token)
151
  assert access_token is not None
152
- assert access_token.client_id == "test-user"
 
 
 
 
 
153
 
154
  async def test_jwks_token_validation_with_invalid_key(
155
  self,
 
141
  url="https://test.example.com/.well-known/jwks.json",
142
  json=mock_jwks_data,
143
  )
144
+
145
+ username = "test-user"
146
+ issuer = "https://test.example.com"
147
+ audience = "https://api.example.com"
148
+
149
  token = rsa_key_pair.create_token(
150
+ subject=username,
151
+ issuer=issuer,
152
+ audience=audience,
153
  )
154
 
155
  access_token = await jwks_provider.load_access_token(token)
156
  assert access_token is not None
157
+ assert access_token.client_id == username
158
+
159
+ # ensure the raw claims are present - #1398
160
+ assert access_token.claims.get("sub") == username
161
+ assert access_token.claims.get("iss") == issuer
162
+ assert access_token.claims.get("aud") == audience
163
 
164
  async def test_jwks_token_validation_with_invalid_key(
165
  self,
tests/server/auth/test_remote_auth_provider.py CHANGED
@@ -1,10 +1,9 @@
1
  import httpx
2
  import pytest
3
- from mcp.server.auth.provider import AccessToken
4
  from pydantic import AnyHttpUrl
5
 
6
  from fastmcp import FastMCP
7
- from fastmcp.server.auth.auth import RemoteAuthProvider, TokenVerifier
8
 
9
 
10
  class SimpleTokenVerifier(TokenVerifier):
 
1
  import httpx
2
  import pytest
 
3
  from pydantic import AnyHttpUrl
4
 
5
  from fastmcp import FastMCP
6
+ from fastmcp.server.auth.auth import AccessToken, RemoteAuthProvider, TokenVerifier
7
 
8
 
9
  class SimpleTokenVerifier(TokenVerifier):
tests/server/auth/test_static_token_verifier.py CHANGED
@@ -1,9 +1,9 @@
1
  """Tests for StaticTokenVerifier integration with FastMCP."""
2
 
3
  import httpx
4
- from mcp.server.auth.provider import AccessToken
5
 
6
  from fastmcp.server import FastMCP
 
7
  from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
8
 
9
 
 
1
  """Tests for StaticTokenVerifier integration with FastMCP."""
2
 
3
  import httpx
 
4
 
5
  from fastmcp.server import FastMCP
6
+ from fastmcp.server.auth.auth import AccessToken
7
  from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
8
 
9
 
tests/server/http/test_bearer_auth_backend.py CHANGED
@@ -2,9 +2,9 @@
2
 
3
  import pytest
4
  from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
5
- from mcp.server.auth.provider import AccessToken
6
  from starlette.requests import HTTPConnection
7
 
 
8
  from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
9
 
10
 
 
2
 
3
  import pytest
4
  from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
 
5
  from starlette.requests import HTTPConnection
6
 
7
+ from fastmcp.server.auth.auth import AccessToken
8
  from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
9
 
10