Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/integrations/supabase.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

mcp = FastMCP("Supabase Protected Server", auth=auth)
Expand Down Expand Up @@ -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")
```
17 changes: 11 additions & 6 deletions src/fastmcp/server/auth/providers/supabase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -72,6 +73,7 @@ def __init__(
*,
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,
Expand All @@ -81,6 +83,8 @@ def __init__(
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.
Expand All @@ -90,6 +94,7 @@ def __init__(
"""
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 = (
Expand All @@ -99,16 +104,16 @@ def __init__(
# 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,
)

# 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,
)

Expand All @@ -133,7 +138,7 @@ async def oauth_authorization_server_metadata(request):
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()
Expand Down
24 changes: 23 additions & 1 deletion tests/server/auth/providers/test_supabase.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_jwt_verifier_configured_correctly(self):
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
Expand Down Expand Up @@ -127,6 +127,28 @@ def test_algorithm_from_parameter(self):
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(
Expand Down