Skip to content

Commit f540385

Browse files
authored
feat: add algorithm configuration to Supabase auth provider (#2376)
1 parent 48fc8cb commit f540385

File tree

2 files changed

+57
-4
lines changed

2 files changed

+57
-4
lines changed

src/fastmcp/server/auth/providers/supabase.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from __future__ import annotations
99

10+
from typing import Literal
11+
1012
import httpx
1113
from pydantic import AnyHttpUrl, field_validator
1214
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -32,6 +34,7 @@ class SupabaseProviderSettings(BaseSettings):
3234

3335
project_url: AnyHttpUrl
3436
base_url: AnyHttpUrl
37+
algorithm: Literal["HS256", "RS256", "ES256"] = "ES256"
3538
required_scopes: list[str] | None = None
3639

3740
@field_validator("required_scopes", mode="before")
@@ -52,13 +55,19 @@ class SupabaseProvider(RemoteAuthProvider):
5255
1. Supabase Project Setup:
5356
- Create a Supabase project at https://supabase.com
5457
- Note your project URL (e.g., "https://abc123.supabase.co")
55-
- For projects created after May 1st, 2025, asymmetric RS256 keys are used by default
56-
- For older projects, consider migrating to asymmetric keys for better security
58+
- Configure your JWT algorithm in Supabase Auth settings (HS256, RS256, or ES256)
59+
- Asymmetric keys (RS256/ES256) are recommended for production
5760
5861
2. JWT Verification:
5962
- FastMCP verifies JWTs using the JWKS endpoint at {project_url}/auth/v1/.well-known/jwks.json
6063
- JWTs are issued by {project_url}/auth/v1
6164
- Tokens are cached for up to 10 minutes by Supabase's edge servers
65+
- Algorithm must match your Supabase Auth configuration
66+
67+
3. Authorization:
68+
- Supabase uses Row Level Security (RLS) policies for database authorization
69+
- OAuth-level scopes are an upcoming feature in Supabase Auth
70+
- Both approaches will be supported once scope handling is available
6271
6372
For detailed setup instructions, see:
6473
https://supabase.com/docs/guides/auth/jwts
@@ -71,6 +80,7 @@ class SupabaseProvider(RemoteAuthProvider):
7180
supabase_auth = SupabaseProvider(
7281
project_url="https://abc123.supabase.co",
7382
base_url="https://your-fastmcp-server.com",
83+
algorithm="ES256", # Match your Supabase Auth configuration
7484
)
7585
7686
# Use with FastMCP
@@ -83,6 +93,7 @@ def __init__(
8393
*,
8494
project_url: AnyHttpUrl | str | NotSetT = NotSet,
8595
base_url: AnyHttpUrl | str | NotSetT = NotSet,
96+
algorithm: Literal["HS256", "RS256", "ES256"] | NotSetT = NotSet,
8697
required_scopes: list[str] | NotSetT | None = NotSet,
8798
token_verifier: TokenVerifier | None = None,
8899
):
@@ -91,7 +102,11 @@ def __init__(
91102
Args:
92103
project_url: Your Supabase project URL (e.g., "https://abc123.supabase.co")
93104
base_url: Public URL of this FastMCP server
94-
required_scopes: Optional list of scopes to require for all requests
105+
algorithm: JWT signing algorithm (HS256, RS256, or ES256). Must match your
106+
Supabase Auth configuration. Defaults to ES256.
107+
required_scopes: Optional list of scopes to require for all requests.
108+
Note: Supabase currently uses RLS policies for authorization. OAuth-level
109+
scopes are an upcoming feature.
95110
token_verifier: Optional token verifier. If None, creates JWT verifier for Supabase
96111
"""
97112
settings = SupabaseProviderSettings.model_validate(
@@ -100,6 +115,7 @@ def __init__(
100115
for k, v in {
101116
"project_url": project_url,
102117
"base_url": base_url,
118+
"algorithm": algorithm,
103119
"required_scopes": required_scopes,
104120
}.items()
105121
if v is not NotSet
@@ -114,7 +130,7 @@ def __init__(
114130
token_verifier = JWTVerifier(
115131
jwks_uri=f"{self.project_url}/auth/v1/.well-known/jwks.json",
116132
issuer=f"{self.project_url}/auth/v1",
117-
algorithm="ES256", # Supabase uses ES256 for asymmetric keys
133+
algorithm=settings.algorithm,
118134
required_scopes=settings.required_scopes,
119135
)
120136

tests/server/auth/providers/test_supabase.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,43 @@ def test_authorization_servers_configured(self):
113113
== "https://abc123.supabase.co/auth/v1"
114114
)
115115

116+
@pytest.mark.parametrize(
117+
"algorithm",
118+
["HS256", "RS256", "ES256"],
119+
)
120+
def test_algorithm_configuration(self, algorithm):
121+
"""Test that algorithm can be configured for different JWT signing methods."""
122+
provider = SupabaseProvider(
123+
project_url="https://abc123.supabase.co",
124+
base_url="https://myserver.com",
125+
algorithm=algorithm,
126+
)
127+
128+
assert provider.token_verifier.algorithm == algorithm # type: ignore[attr-defined]
129+
130+
def test_algorithm_default_es256(self):
131+
"""Test that algorithm defaults to ES256 when not specified."""
132+
provider = SupabaseProvider(
133+
project_url="https://abc123.supabase.co",
134+
base_url="https://myserver.com",
135+
)
136+
137+
assert provider.token_verifier.algorithm == "ES256" # type: ignore[attr-defined]
138+
139+
def test_algorithm_from_env_var(self):
140+
"""Test that algorithm can be configured via environment variable."""
141+
with patch.dict(
142+
os.environ,
143+
{
144+
"FASTMCP_SERVER_AUTH_SUPABASE_PROJECT_URL": "https://env123.supabase.co",
145+
"FASTMCP_SERVER_AUTH_SUPABASE_BASE_URL": "https://envserver.com",
146+
"FASTMCP_SERVER_AUTH_SUPABASE_ALGORITHM": "RS256",
147+
},
148+
):
149+
provider = SupabaseProvider()
150+
151+
assert provider.token_verifier.algorithm == "RS256" # type: ignore[attr-defined]
152+
116153

117154
def run_mcp_server(host: str, port: int) -> None:
118155
mcp = FastMCP(

0 commit comments

Comments
 (0)