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
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@ export const auth: NavMenuConstant = {
},
],
},
{
name: 'OAuth 2.1 Server',
items: [
{ name: 'Overview', url: '/guides/auth/oauth-server' },
{ name: 'Getting Started', url: '/guides/auth/oauth-server/getting-started' },
{ name: 'OAuth Flows', url: '/guides/auth/oauth-server/oauth-flows' },
{ name: 'MCP Authentication', url: '/guides/auth/oauth-server/mcp-authentication' },
{ name: 'Token Security & RLS', url: '/guides/auth/oauth-server/token-security' },
],
},
{
name: 'Third-party auth',
enabled: allAuthProvidersEnabled,
Expand Down
65 changes: 65 additions & 0 deletions apps/docs/content/_partials/api_rate_limits.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## Rate limits

Rate limits are applied to prevent abuse and ensure fair usage of the Management API. Rate limits are based on a per-user, per-scope model, meaning each user gets independent rate limits for each project and organization they interact with.

### Standard rate limit

| Limit | Duration | Scope |
| ------------ | -------- | ---------------------------------- |
| 120 requests | 1 minute | Per user, per project/organization |

When you exceed this rate limit, all subsequent API calls will return a `429 Too Many Requests` response for the remainder of the minute. Once the time window expires, your request quota resets and you can make requests again.

### Rate limit scope

Rate limits are applied with per-user + per-scope isolation:

- **Project scope**: Rate limits apply independently to each project. Requests to one project do not count toward the limit of another project.
- **Organization scope**: Rate limits apply independently to each organization. Requests to one organization do not count toward the limit of another organization.

This means you can make 120 requests to Project A and 120 requests to Project B within the same minute without hitting rate limits, as they are tracked separately.

### Rate limit response headers

Every API response includes rate limit information in the following headers:

- `X-RateLimit-Limit` - The maximum number of requests allowed in the current time window
- `X-RateLimit-Remaining` - The number of requests remaining before you hit the rate limit
- `X-RateLimit-Reset` - The number of milliseconds remaining until your rate limit resets

You can use these headers to monitor your usage and implement proactive rate limit handling before receiving a 429 response.

### How rate limits are tracked

Your requests are identified and tracked using one of the following identifiers, in this order of priority:

1. **OAuth App ID** - If your request is authenticated via an OAuth application
2. **User ID** - If your request is authenticated with a personal access token
3. **IP Address** - If your request is unauthenticated (extracted from request headers)

Each identifier is combined with the scope (project or organization) to create a unique tracking key. This ensures that rate limits are isolated per user and per scope, preventing one project or organization from affecting another.

### Endpoint exceptions

Some endpoints have stricter rate limits than the standard 120 requests per minute to prevent abuse of resource-intensive operations:

| Endpoint | Limit | Duration | Reason |
| ---------------------------------------------------------- | ----------- | -------- | --------------------------------------------------- |
| `GET /v1/projects/:ref/endpoints/logs.all` | 30 requests | 1 minute | Analytics log queries are computationally expensive |
| `GET /v1/projects/:ref/endpoints/usage.api-counts` | 30 requests | 1 minute | Analytics aggregation is computationally expensive |
| `GET /v1/projects/:ref/endpoints/usage.api-requests-count` | 30 requests | 1 minute | Analytics aggregation is computationally expensive |
| `GET /v1/projects/:ref/database/context` | 10 requests | 1 minute | Database context operations are resource-intensive |
| `GET /v1/projects/:ref/database/context` | 1 request | 1 second | Burst limit to prevent rapid successive requests |
| `POST /v1/projects/:ref/config/custom-hostname/initialize` | 10 requests | 1 minute | These operations are expensive |
| `POST /v1/projects/:ref/config/custom-hostname/reverify` | 10 requests | 1 minute | These operations are expensive |
| `DELETE /v1/projects/:ref/config/custom-hostname` | 10 requests | 1 minute | These operations are expensive |
| `GET /v1/projects/:ref/config/vanity-subdomain` | 10 requests | 1 minute | These operations are expensive |

**Note:** The `GET /v1/projects/:ref/database/context` endpoint has dual rate limiting. You can make up to 10 requests per minute, but also no more than 1 request per second to prevent burst traffic.

### Best practices

- **Monitor rate limit headers** - Check the `X-RateLimit-Remaining` header to see how many requests you have left. When it approaches 0, slow down your requests to avoid hitting the limit.
- **Implement exponential backoff** - When you receive a 429 response, wait before retrying. You can use the `X-RateLimit-Reset` header (milliseconds) to determine exactly how long to wait.
- **Batch operations** - Where possible, combine multiple operations into fewer API calls to reduce your request count.
- **Be mindful of expensive endpoints** - Analytics, database context, and domain endpoints have stricter limits, so use them judiciously.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ Optional Claims: `jti`, `nbf`, `app_metadata`, `user_metadata`, `amr`,

**Inputs**

| Field | Type | Description |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user_id` | `string` | Unique identifier for the user attempting to sign in. |
| `claims` | `object` | Claims which are included in the access token. |
| `authentication_method` | `string` | The authentication method used to request the access token. Possible values include: `oauth`, `password`, `otp`, `totp`, `recovery`, `invite`, `sso/saml`, `magiclink`, `email/signup`, `email_change`, `token_refresh`, `anonymous`. |
| Field | Type | Description |
| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user_id` | `string` | Unique identifier for the user attempting to sign in. |
| `claims` | `object` | Claims which are included in the access token. |
| `authentication_method` | `string` | The authentication method used to request the access token. Possible values include: `oauth`, `password`, `otp`, `totp`, `recovery`, `invite`, `sso/saml`, `magiclink`, `email/signup`, `email_change`, `token_refresh`, `oauth_provider/authorization_code`, `anonymous`. |

<Tabs
scrollable
Expand All @@ -45,7 +45,8 @@ Optional Claims: `jti`, `nbf`, `app_metadata`, `user_metadata`, `amr`,
"aal": "aal1",
"amr": [ { "method": "anonymous", "timestamp": 1715686621 } ],
"session_id": "4b938a09-5372-4177-a314-cfa292099ea2",
"is_anonymous": true
"is_anonymous": true,
"client_id": "oauth-client-id-if-oauth-flow"
},
"authentication_method": "anonymous"
}
Expand Down Expand Up @@ -143,6 +144,10 @@ Optional Claims: `jti`, `nbf`, `app_metadata`, `user_metadata`, `amr`,
"is_anonymous": {
"type": "boolean",
"x-faker": "random.boolean"
},
"client_id": {
"type": "string",
"x-faker": "random.uuid"
}
},
"required": [
Expand Down Expand Up @@ -175,6 +180,7 @@ Optional Claims: `jti`, `nbf`, `app_metadata`, `user_metadata`, `amr`,
"email/signup",
"email_change",
"token_refresh",
"oauth_provider/authorization_code",
"anonymous"
]
}
Expand Down
91 changes: 91 additions & 0 deletions apps/docs/content/guides/auth/oauth-server.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: 'OAuth 2.1 Server'
description: 'Turn your Supabase project into an OAuth 2.1 and OpenID Connect identity provider'
---

Supabase Auth can act as an OAuth 2.1 and OpenID Connect (OIDC) identity provider. This allows other applications and services to use your Supabase project as their authentication provider, just like "Sign in with Google" or "Sign in with GitHub".

You can use this to build "Sign in with [Your App]" experiences, authenticate AI agents through the Model Context Protocol (MCP), power developer platforms with third-party integrations, or implement standards-compliant enterprise SSO.

## Use cases

There are several reasons why you might want to enable OAuth 2.1 Server in your Supabase project:

- **Developer platforms and marketplaces**: Allow third-party developers to build integrations and apps for your platform. Partners can offer "Sign in with [Your App]" to their users, with your control over data access through Row Level Security policies.

- **AI agents and automation**: Authenticate AI agents, LLM tools, and MCP servers that need to access user data. The Model Context Protocol provides automatic OAuth discovery and client registration for AI applications.

- **Mobile and desktop apps**: Issue OAuth tokens to your own mobile apps, desktop applications, or other first-party clients. All tokens respect your existing Row Level Security policies and work with Custom Access Token Hooks.

- **Enterprise SSO**: Provide OpenID Connect (OIDC) authentication for enterprise customers who need standards-compliant identity federation across multiple services.

## Overview

Supabase Auth implements the OAuth 2.1 authorization code flow with PKCE (Proof Key for Code Exchange). When a third-party application wants to access user data:

1. The application redirects the user to your authorization endpoint
2. Supabase Auth validates the request and redirects to your custom authorization UI
3. The user authenticates (using any of your enabled auth methods) and approves access
4. Supabase Auth issues an authorization code
5. The application exchanges the code for access and refresh tokens
6. The application uses the access token to make authenticated API requests

Access tokens are standard Supabase JWTs that include `user_id`, `role`, and `client_id` claims. Your existing Row Level Security policies automatically apply to OAuth tokens, giving you fine-grained control over what each client can access.

### Supported standards

- **OAuth 2.1**: Latest OAuth specification with mandatory PKCE
- **OpenID Connect**: ID tokens (with `openid` scope), UserInfo endpoint, and OIDC discovery
- **Standard scopes**: `openid`, `email`, `profile`, and `phone` scopes for controlling data access
- **Dynamic client registration**: Automatic registration for MCP-compatible clients
- **JWKS endpoint**: Public keys for third parties to validate tokens

### Integration with existing auth

OAuth 2.1 Server works seamlessly with your existing Supabase Auth configuration:

- Users can authenticate using any enabled method (password, magic link, social providers, MFA, phone)
- [Custom Access Token Hooks](/guides/auth/auth-hooks/access-token-hook) apply to OAuth tokens, allowing you to customize claims like `audience` or add client-specific permissions
- Row Level Security policies control data access using the `client_id` claim in tokens
- All standard Supabase features (email templates, hooks, rate limiting) continue to work

## Set up OAuth 2.1 server

To enable OAuth 2.1 Server in your project, follow these guides:

<div className="grid md:grid-cols-12 gap-4 not-prose">
{[
{
name: 'Getting Started',
description:
'Enable OAuth 2.1, configure your authorization endpoint, and register your first client.',
href: '/guides/auth/oauth-server/getting-started',
},
{
name: 'OAuth Flows',
description: 'Detailed walkthrough of authorization code and refresh token flows.',
href: '/guides/auth/oauth-server/oauth-flows',
},
{
name: 'MCP Authentication',
description: 'Authenticate AI agents and LLM tools using Model Context Protocol.',
href: '/guides/auth/oauth-server/mcp-authentication',
},
{
name: 'Token Security & RLS',
description: 'Control data access with Row Level Security policies for OAuth clients.',
href: '/guides/auth/oauth-server/token-security',
},
].map((x) => (
<div className="col-span-6" key={x.href}>
<Link href={x.href} passHref>
<GlassPanel title={x.name}>{x.description}</GlassPanel>
</Link>
</div>
))}
</div>

## Resources

- [GitHub Discussion](https://github.com/orgs/supabase/discussions/38022) - Share your use cases and help shape the roadmap
- [Discord Community](https://discord.supabase.com/) - Get help and share what you're building
Loading
Loading