mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 21:14:17 +02:00
Feature/supabase custom auth route (#2632)
Co-authored-by: Eloi Zalczer <eloi@entropia.io> Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
This commit is contained in:
parent
a3123176b3
commit
fb11282e9b
3 changed files with 41 additions and 14 deletions
|
|
@ -17,7 +17,7 @@ This guide shows you how to secure your FastMCP server using **Supabase Auth**.
|
|||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. A **[Supabase Account](https://supabase.com/)** with a project
|
||||
1. A **[Supabase Account](https://supabase.com/)** with a project or a self-hosted **Supabase Auth** instance
|
||||
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
|
||||
|
||||
### Step 1: Get Supabase Project URL
|
||||
|
|
@ -37,7 +37,9 @@ from fastmcp.server.auth.providers.supabase import SupabaseProvider
|
|||
# Configure Supabase Auth
|
||||
auth = SupabaseProvider(
|
||||
project_url="https://abc123.supabase.co",
|
||||
base_url="http://localhost:8000"
|
||||
base_url="http://localhost:8000",
|
||||
# Optional: customize auth_route for self-hosted Supabase Auth with custom routes
|
||||
# auth_route="/my/auth/route"
|
||||
)
|
||||
|
||||
mcp = FastMCP("Supabase Protected Server", auth=auth)
|
||||
|
|
@ -101,12 +103,10 @@ from fastmcp.server.auth.providers.supabase import SupabaseProvider
|
|||
|
||||
# Load configuration from environment variables
|
||||
auth = SupabaseProvider(
|
||||
project_url=os.environ.get("SUPABASE_PROJECT_URL"),
|
||||
base_url=os.environ.get("BASE_URL", "https://your-server.com")
|
||||
project_url=os.environ["SUPABASE_PROJECT_URL"],
|
||||
base_url=os.environ.get("BASE_URL", "https://your-server.com"),
|
||||
auth_route=os.environ.get("SUPABASE_AUTH_ROUTE", "/auth/v1"), # Optional: for custom routes
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Supabase Secured App", auth=auth)
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="Supabase Protected Server")
|
||||
```
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
- Asymmetric keys (RS256/ES256) are recommended for production
|
||||
|
||||
2. JWT Verification:
|
||||
- FastMCP verifies JWTs using the JWKS endpoint at {project_url}/auth/v1/.well-known/jwks.json
|
||||
- JWTs are issued by {project_url}/auth/v1
|
||||
- FastMCP verifies JWTs using the JWKS endpoint at {project_url}{auth_route}/.well-known/jwks.json
|
||||
- JWTs are issued by {project_url}{auth_route}
|
||||
- Default auth_route is "/auth/v1" (can be customized for self-hosted setups)
|
||||
- Tokens are cached for up to 10 minutes by Supabase's edge servers
|
||||
- Algorithm must match your Supabase Auth configuration
|
||||
|
||||
|
|
@ -72,6 +73,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
*,
|
||||
project_url: AnyHttpUrl | str,
|
||||
base_url: AnyHttpUrl | str,
|
||||
auth_route: str = "/auth/v1",
|
||||
algorithm: Literal["HS256", "RS256", "ES256"] = "ES256",
|
||||
required_scopes: list[str] | None = None,
|
||||
token_verifier: TokenVerifier | None = None,
|
||||
|
|
@ -81,6 +83,8 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
Args:
|
||||
project_url: Your Supabase project URL (e.g., "https://abc123.supabase.co")
|
||||
base_url: Public URL of this FastMCP server
|
||||
auth_route: Supabase Auth route. Defaults to "/auth/v1". Can be customized
|
||||
for self-hosted Supabase Auth setups using custom routes.
|
||||
algorithm: JWT signing algorithm (HS256, RS256, or ES256). Must match your
|
||||
Supabase Auth configuration. Defaults to ES256.
|
||||
required_scopes: Optional list of scopes to require for all requests.
|
||||
|
|
@ -90,6 +94,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
"""
|
||||
self.project_url = str(project_url).rstrip("/")
|
||||
self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
|
||||
self.auth_route = auth_route.strip("/")
|
||||
|
||||
# Parse scopes if provided as string
|
||||
parsed_scopes = (
|
||||
|
|
@ -99,8 +104,8 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
# Create default JWT verifier if none provided
|
||||
if token_verifier is None:
|
||||
token_verifier = JWTVerifier(
|
||||
jwks_uri=f"{self.project_url}/auth/v1/.well-known/jwks.json",
|
||||
issuer=f"{self.project_url}/auth/v1",
|
||||
jwks_uri=f"{self.project_url}/{self.auth_route}/.well-known/jwks.json",
|
||||
issuer=f"{self.project_url}/{self.auth_route}",
|
||||
algorithm=algorithm,
|
||||
required_scopes=parsed_scopes,
|
||||
)
|
||||
|
|
@ -108,7 +113,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
# Initialize RemoteAuthProvider with Supabase as the authorization server
|
||||
super().__init__(
|
||||
token_verifier=token_verifier,
|
||||
authorization_servers=[AnyHttpUrl(f"{self.project_url}/auth/v1")],
|
||||
authorization_servers=[AnyHttpUrl(f"{self.project_url}/{self.auth_route}")],
|
||||
base_url=self.base_url,
|
||||
)
|
||||
|
||||
|
|
@ -133,7 +138,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"{self.project_url}/auth/v1/.well-known/oauth-authorization-server"
|
||||
f"{self.project_url}/{self.auth_route}/.well-known/oauth-authorization-server"
|
||||
)
|
||||
response.raise_for_status()
|
||||
metadata = response.json()
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class TestSupabaseProvider:
|
|||
base_url="https://myserver.com",
|
||||
)
|
||||
|
||||
# Check that JWT verifier uses the correct endpoints
|
||||
# Check that JWT verifier uses the correct endpoints (default auth_route)
|
||||
assert isinstance(provider.token_verifier, JWTVerifier)
|
||||
assert (
|
||||
provider.token_verifier.jwks_uri
|
||||
|
|
@ -127,6 +127,28 @@ class TestSupabaseProvider:
|
|||
assert isinstance(provider.token_verifier, JWTVerifier)
|
||||
assert provider.token_verifier.algorithm == "RS256"
|
||||
|
||||
def test_custom_auth_route(self):
|
||||
provider = SupabaseProvider(
|
||||
project_url="https://abc123.supabase.co",
|
||||
base_url="https://myserver.com",
|
||||
auth_route="/custom/auth/route",
|
||||
)
|
||||
|
||||
assert provider.auth_route == "custom/auth/route"
|
||||
assert (
|
||||
provider.token_verifier.jwks_uri
|
||||
== "https://abc123.supabase.co/custom/auth/route/.well-known/jwks.json"
|
||||
) # type: ignore[attr-defined]
|
||||
|
||||
def test_custom_auth_route_trailing_slash(self):
|
||||
provider = SupabaseProvider(
|
||||
project_url="https://abc123.supabase.co",
|
||||
base_url="https://myserver.com",
|
||||
auth_route="/custom/auth/route/",
|
||||
)
|
||||
|
||||
assert provider.auth_route == "custom/auth/route"
|
||||
|
||||
|
||||
def run_mcp_server(host: str, port: int) -> None:
|
||||
mcp = FastMCP(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue