Jeremiah Lowin commited on
Commit
5d876b5
·
unverified ·
2 Parent(s): 8e4c9721839cc8

Merge pull request #892 from jlowin/claude-wt-20250620-113451

Browse files
src/fastmcp/server/auth/providers/bearer.py CHANGED
@@ -17,7 +17,7 @@ from mcp.shared.auth import (
17
  OAuthClientInformationFull,
18
  OAuthToken,
19
  )
20
- from pydantic import SecretStr
21
 
22
  from fastmcp.server.auth.auth import (
23
  ClientRegistrationOptions,
@@ -179,8 +179,16 @@ class BearerAuthProvider(OAuthProvider):
179
  if public_key and jwks_uri:
180
  raise ValueError("Provide either public_key or jwks_uri, not both")
181
 
 
 
 
 
 
 
 
 
182
  super().__init__(
183
- issuer_url=issuer or "https://fastmcp.example.com",
184
  client_registration_options=ClientRegistrationOptions(enabled=False),
185
  revocation_options=RevocationOptions(enabled=False),
186
  required_scopes=required_scopes,
 
17
  OAuthClientInformationFull,
18
  OAuthToken,
19
  )
20
+ from pydantic import AnyHttpUrl, SecretStr, ValidationError
21
 
22
  from fastmcp.server.auth.auth import (
23
  ClientRegistrationOptions,
 
179
  if public_key and jwks_uri:
180
  raise ValueError("Provide either public_key or jwks_uri, not both")
181
 
182
+ # Only pass issuer to parent if it's a valid URL, otherwise use default
183
+ # This allows the issuer claim validation to work with string issuers per RFC 7519
184
+ try:
185
+ issuer_url = AnyHttpUrl(issuer) if issuer else "https://fastmcp.example.com"
186
+ except ValidationError:
187
+ # Issuer is not a valid URL, use default for parent class
188
+ issuer_url = "https://fastmcp.example.com"
189
+
190
  super().__init__(
191
+ issuer_url=issuer_url,
192
  client_registration_options=ClientRegistrationOptions(enabled=False),
193
  revocation_options=RevocationOptions(enabled=False),
194
  required_scopes=required_scopes,
tests/auth/providers/test_bearer.py CHANGED
@@ -539,6 +539,59 @@ class TestBearerToken:
539
  assert access_token is not None
540
  assert access_token.client_id == "app456" # Should prefer client_id over sub
541
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
 
543
  class TestFastMCPBearerAuth:
544
  def test_bearer_auth(self):
 
539
  assert access_token is not None
540
  assert access_token.client_id == "app456" # Should prefer client_id over sub
541
 
542
+ async def test_string_issuer_validation(self, rsa_key_pair: RSAKeyPair):
543
+ """Test that string (non-URL) issuers are supported per RFC 7519."""
544
+ # Create provider with string issuer
545
+ provider = BearerAuthProvider(
546
+ public_key=rsa_key_pair.public_key,
547
+ issuer="my-service", # String issuer, not a URL
548
+ )
549
+
550
+ # Create token with matching string issuer
551
+ token = rsa_key_pair.create_token(
552
+ subject="test-user",
553
+ issuer="my-service", # Same string issuer
554
+ )
555
+
556
+ access_token = await provider.load_access_token(token)
557
+ assert access_token is not None
558
+ assert access_token.client_id == "test-user"
559
+
560
+ async def test_string_issuer_mismatch_rejection(self, rsa_key_pair: RSAKeyPair):
561
+ """Test that mismatched string issuers are rejected."""
562
+ # Create provider with one string issuer
563
+ provider = BearerAuthProvider(
564
+ public_key=rsa_key_pair.public_key,
565
+ issuer="my-service",
566
+ )
567
+
568
+ # Create token with different string issuer
569
+ token = rsa_key_pair.create_token(
570
+ subject="test-user",
571
+ issuer="other-service", # Different string issuer
572
+ )
573
+
574
+ access_token = await provider.load_access_token(token)
575
+ assert access_token is None
576
+
577
+ async def test_url_issuer_still_works(self, rsa_key_pair: RSAKeyPair):
578
+ """Test that URL issuers still work after the fix."""
579
+ # Create provider with URL issuer
580
+ provider = BearerAuthProvider(
581
+ public_key=rsa_key_pair.public_key,
582
+ issuer="https://my-auth-server.com", # URL issuer
583
+ )
584
+
585
+ # Create token with matching URL issuer
586
+ token = rsa_key_pair.create_token(
587
+ subject="test-user",
588
+ issuer="https://my-auth-server.com", # Same URL issuer
589
+ )
590
+
591
+ access_token = await provider.load_access_token(token)
592
+ assert access_token is not None
593
+ assert access_token.client_id == "test-user"
594
+
595
 
596
  class TestFastMCPBearerAuth:
597
  def test_bearer_auth(self):