quickoauth
← All guides

Add OAuth to your MCP server with Supabase Auth

Supabase's OAuth Server beta as an MCP authorization server: the /auth/v1 issuer, ES256 keys, dynamic client registration, and the dashboard toggle that only the Management API can flip. Executed live against a real project.

Updated

On this page

Supabase Auth grew an OAuth Server feature (beta) that makes your project an OAuth 2.1 authorization server: if you already keep users in Supabase, your MCP connector can log in with them directly. We enabled it on a fresh project, hit one real dashboard bug (with the workaround), and validated an ES256 Supabase JWT inside a Worker MCP server. Testbed: https://qo-test-supabase.rough-disk-9b56.workers.dev/mcp.

1. Project setup

Sign-up is free (GitHub/ChatGPT OAuth or email+password; the email path is behind an hCaptcha). Create a project; the free plan is plenty.

New project form

2. Enable the OAuth server

Authentication → Configuration → OAuth Server (BETA): toggle it on. The page then reveals the endpoints:

OAuth Server settings

  • Authorization endpoint: https://<ref>.supabase.co/auth/v1/oauth/authorize
  • Token endpoint: https://<ref>.supabase.co/auth/v1/oauth/token
  • JWKS: https://<ref>.supabase.co/auth/v1/.well-known/jwks.json
  • Discovery: …/auth/v1/.well-known/openid-configuration and …/auth/v1/.well-known/oauth-authorization-server

The Authorization Path is a route in your app (we used /authorize) where you render login + consent with supabase-js; Supabase redirects there, your page calls the approve API. Set your Site URL (Auth → URL Configuration) to the app that hosts it.

3. The issuer lives under /auth/v1

The issuer is https://<ref>.supabase.co/auth/v1, not the project root. Your protected-resource metadata must advertise exactly that in authorization_servers, and clients will fetch /auth/v1/.well-known/oauth-authorization-server relative to it. Both RFC 8414 and OIDC discovery documents are served; S256 is supported; registration_endpoint appears once DCR is on.

4. Dynamic client registration, and the toggle that wouldn’t stick

The “Allow Dynamic OAuth Apps” switch in the dashboard refused to persist for us (beta UI bug: click, save, still off). The Management API sets the same field reliably:

curl -X PATCH https://api.supabase.com/v1/projects/$REF/config/auth \
  -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"oauth_server_allow_dynamic_registration": true}'

($SUPABASE_ACCESS_TOKEN is a personal access token from Account → Access Tokens.) Then registration works exactly the way Claude does it:

curl -X POST https://$REF.supabase.co/auth/v1/oauth/clients/register \
  -H 'content-type: application/json' -d '{
  "client_name": "probe",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"]
}'
# → 201, uuid client_id, "registration_type": "dynamic", no null fields

Registered apps appear under Authentication → OAuth Apps. There’s no RFC 7592 management URI in the response, so probe clients accumulate until you delete them in the dashboard.

5. Validate the tokens: ES256, aud is not your URL

Two things break naive JWT middleware here:

  • Supabase signs with ES256 (P-256), not RS256, so make sure your verifier imports EC keys from the JWKS. (Projects still on the legacy shared-secret HS256 setup must migrate to JWT signing keys first.)
  • aud is "authenticated" (the Supabase role), not your MCP URL; validate iss + signature + exp, not audience-as-resource.

The rest is the standard resource-server contract: PRM document, 401 with a WWW-Authenticate challenge, 401 for garbage tokens.

6. Headless proof for CI

You never need a browser to prove the chain: enable mailer_autoconfirm (Management API), create a user, and use the classic password grant with your project’s anon key:

curl -X POST "https://$REF.supabase.co/auth/v1/token?grant_type=password" \
  -H "apikey: $ANON_KEY" -H 'content-type: application/json' \
  -d '{"email":"demo@example.com","password":"…"}'

The access_token that returns is the same ES256 JWT the OAuth server issues; send it to your MCP endpoint. Our RL suite replays discovery, DCR, token mint, and the authenticated MCP call against this project on every deploy: 92 live checks across five deployments at the time of writing.

Next steps

To take this from testbed to your real connector:

  1. Build the /authorize consent page with supabase-js at your configured authorization path; that’s the one piece this guide stubs.
  2. Run the four-curl self-test from the connector checklist, remembering every well-known URL lives under /auth/v1.
  3. Connect in claude.ai under Customize → Connectors; then widen the allowlist for ChatGPT and Grok with one server, three connectors.

The Supabase OAuth prompt (supabase-oauth-mcp) hands the whole sequence to your agent, Management-API workaround included. If a step fails, use debugging connector failures. To compare providers, see Auth0 and Stytch, or go fully self-hosted with Hydra.

Put it into practice

Supabase Auth OAuth Server for an MCP connector

Enable Supabase's OAuth Server beta, force the DCR toggle through the Management API, and validate the ES256 tokens in your MCP server, plus a fully headless CI proof.

Preview prompt
Configure my Supabase project (ref MY_REF [fill in]) as the OAuth 2.1 authorization server for my remote MCP server at https://MY_DOMAIN/mcp, using the OAuth Server beta in Supabase Auth.

Enablement:
- Dashboard: Authentication → Configuration → OAuth Server (BETA) → enable. Set the Authorization Path to /authorize and the Site URL (Auth → URL Configuration) to https://MY_DOMAIN; Supabase will redirect end users to https://MY_DOMAIN/authorize, where my app renders login + consent with supabase-js and approves the authorization.
- Dynamic client registration: the dashboard's "Allow Dynamic OAuth Apps" toggle may fail to persist (beta bug). Set it through the Management API instead, with a personal access token from Account → Access Tokens:
  PATCH https://api.supabase.com/v1/projects/MY_REF/config/auth with {"oauth_server_allow_dynamic_registration": true}, then GET the config back and confirm it stuck.

Discovery facts my metadata must respect:
- The issuer is https://MY_REF.supabase.co/auth/v1, under /auth/v1, NOT the project root. authorization_servers in my protected-resource metadata must be exactly that.
- Both /auth/v1/.well-known/openid-configuration and /auth/v1/.well-known/oauth-authorization-server are served; registration_endpoint is /auth/v1/oauth/clients/register; S256 is supported.

Resource server behaviour (write or adapt my MCP server):
- /.well-known/oauth-protected-resource with resource = https://MY_DOMAIN/mcp and the /auth/v1 issuer as authorization server.
- 401 + WWW-Authenticate Bearer challenge (resource_metadata pointer) when unauthenticated; 401 on invalid tokens.
- Validate JWTs against https://MY_REF.supabase.co/auth/v1/.well-known/jwks.json. Supabase signs with ES256 (P-256), so my verifier must import EC keys, not just RSA. Check iss and exp; do NOT require aud to equal the MCP URL (Supabase sets aud to "authenticated").

Verification (run and show output):
- POST /auth/v1/oauth/clients/register with redirect_uris ["https://claude.ai/api/mcp/auth_callback"], token_endpoint_auth_method "none", grant_types ["authorization_code","refresh_token"] → 201 with a uuid client_id and "registration_type": "dynamic"; no null fields. Registered apps show under Authentication → OAuth Apps; there is no RFC 7592 management URI, so clean probes up in the dashboard.
- Headless CI proof without any browser: PATCH mailer_autoconfirm true via the Management API, create a test user, POST /auth/v1/token?grant_type=password with the anon key → the returned access_token is the same ES256 JWT the OAuth server issues; call my MCP endpoint with it and confirm a garbage token gets 401.

Secrets (access token, service_role, anon key) live in environment variables only, never in code or logs.

Use this site from your agent

Add this URL as a connector in Claude, ChatGPT, or Grok. No sign-in required.

https://quickoauth.com/mcp
Explore more guides →

Search prompts & guides

24 results