mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Closes [DOCS-1289](https://linear.app/supabase/issue/DOCS-1289/get-the-linter-to-fix-what-it-flags-or-retirereplace-the-linter) Stacked on #50600, which points contributors at the authoring skills. Merge that one first. ## Problem Contributors experienced friction with the linter. They felt nickle and dimed for tiny nits and felt detracted from the work itself. PRs would become noisy with tiny one-word suggestions. Additionally, our homegrown linter is not very intelligent, causing frequent overrides. ## Solution This removes the linter entirely in favor of directing contributors to use SKILLS instead. The removal entails... - **CI.** Delete the three `docs_lint` workflows: the PR check, the external-PR comment companion, and the nightly `--fix` bot. Drop the stale `zizmor.yml` ignore entry for the deleted workflow. - **Tooling.** Delete `supa-mdx-lint.config.toml` and the 14 rule files. Drop the `lint:mdx` script and the `@supabase/supa-mdx-lint` dependency from docs, learn, and ui-library, and regenerate the lockfile. - **Content.** Remove the 181 directives. A separate commit carries Prettier's reformatting of the tables and blank lines those comments had suppressed, so the deletion commit stays readable. No prose changes. - **Style guide.** The word list states each rule directly instead of describing what the linter flagged. Every term survives, including the phrase groups that mirrored `Rule004ExcludeWords`. - **Skills.** `write-the-docs`, `edit-the-docs`, and `review-the-docs` drop `pnpm lint:mdx` from their self-review commands and check the word list directly. `ask-the-docs`'s CI reference drops both workflows. ## Manual testing 1. Run `git grep -i supa-mdx-lint -- . ':!pnpm-lock.yaml'`. No matches. 2. Run `pnpm install --frozen-lockfile --lockfile-only`. It passes, so the lockfile matches the three trimmed manifests. 3. Run `git diff master...HEAD --name-only --diff-filter=ACMR | grep -E '\.(md|mdx)$' | xargs npx prettier --config prettier.config.mjs --check`. All changed markdown passes. 4. Open the [reformatted filter table](https://docs-git-docs-retire-mdx-linter-supabase.vercel.app/docs/guides/observability/logs#filter-events) on the preview and compare it with [production](https://supabase.com/docs/guides/observability/logs#filter-events). The table renders the same. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Documentation guidance now uses manual prose and terminology review with the shared word list. * Clarified storage configuration and common Realtime channel mistakes. * Improved table formatting, text wrapping, and selected reference links. * Updated documentation authoring and review guidance. * **Chores** * Retired automated MDX linting from workflows and local validation commands. * Removed lint-suppression markers throughout documentation without changing instructions. * Added targeted documentation review guidance for pull requests. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
216 lines
11 KiB
Plaintext
216 lines
11 KiB
Plaintext
---
|
|
title: 'New API Keys and Asymmetric Authentication'
|
|
description: 'Configure new API keys and ES256 asymmetric authentication for self-hosted Supabase.'
|
|
subtitle: 'Configure new API keys and ES256 asymmetric authentication for self-hosted Supabase.'
|
|
---
|
|
|
|
You can configure self-hosted Supabase to use the [publishable and secret API keys](/docs/guides/getting-started/api-keys) alongside the legacy API keys (`ANON_KEY` and `SERVICE_ROLE_KEY` HS256-signed JWTs).
|
|
|
|
## Before you begin
|
|
|
|
- Complete the [Docker setup guide](/docs/guides/self-hosting/docker) so that `JWT_SECRET`, `ANON_KEY`, and `SERVICE_ROLE_KEY` are set in your `.env` file. [Quick start (Linux)](/docs/guides/self-hosting/docker#quick-start-linux) handles this automatically; the manual path runs [`generate-keys.sh`](/docs/guides/self-hosting/docker#generate-keys-and-secrets).
|
|
- If you are upgrading from a legacy self-hosted Supabase environment, make sure to check the [changelog](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md#2026-03-16) and [add/update](/docs/guides/self-hosting/updating) the following files:
|
|
- `.env.example` (merge new sections into your `.env` file)
|
|
- `docker-compose.yml`
|
|
- `utils/add-new-auth-keys.sh`
|
|
- `utils/rotate-new-api-keys.sh`
|
|
- `volumes/api/envoy/*`
|
|
|
|
## Adding the new keys
|
|
|
|
From your project directory where you have `docker-compose.yml`:
|
|
|
|
```sh
|
|
sh utils/add-new-auth-keys.sh --update-env
|
|
```
|
|
|
|
This generates new configuration environment variables and writes them to `.env`. Omit `--update-env` to review and confirm the changes interactively.
|
|
|
|
<Admonition type="caution">
|
|
|
|
The script reads `JWT_SECRET` from `.env` and includes it as a symmetric key inside both `JWT_KEYS` and `JWT_JWKS`. If you later change `JWT_SECRET`, you must regenerate the JWKS as well.
|
|
|
|
</Admonition>
|
|
|
|
In addition, the following configuration is uncommented automatically by the script for the new authentication to work correctly:
|
|
|
|
```yaml name=docker-compose.yml
|
|
auth:
|
|
environment:
|
|
# JSON array of signing JWKs (EC private + legacy symmetric)
|
|
GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}
|
|
|
|
rest:
|
|
environment:
|
|
# PostgREST accepts a plain-text symmetric secret, a single JWK, or a JWKS.
|
|
PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}
|
|
|
|
realtime:
|
|
environment:
|
|
# JWKS for token verification (EC public + legacy symmetric)
|
|
API_JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}
|
|
|
|
storage:
|
|
environment:
|
|
# JWKS for token verification (EC public + legacy symmetric)
|
|
JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}
|
|
|
|
functions:
|
|
environment:
|
|
# JWKS for token verification (EC public + legacy symmetric).
|
|
SUPABASE_JWKS: ${JWT_JWKS:-{"keys":[]}}
|
|
```
|
|
|
|
<Admonition type="caution">
|
|
|
|
Nested variable interpolation (`${A:-${B}}`) requires `podman-compose >= 1.6.0`. Earlier versions (still shipped by some Linux distributions) do not support it - if you are on an older `podman-compose`, either upgrade or replace each nested expression with the required variable directly, see the inline comments in `docker-compose.yml` for the exact substitutions.
|
|
|
|
</Admonition>
|
|
|
|
Restart all services:
|
|
|
|
```sh
|
|
sh run.sh recreate
|
|
```
|
|
|
|
### New API keys format
|
|
|
|
These keys use the same format as [API keys](/docs/guides/getting-started/api-keys) on the Supabase platform:
|
|
|
|
```
|
|
sb_publishable_<22-char-random>_<8-char-checksum>
|
|
sb_secret_<22-char-random>_<8-char-checksum>
|
|
```
|
|
|
|
### Verifying the setup
|
|
|
|
Test with the new secret key:
|
|
|
|
```sh
|
|
curl http://<your-domain>/rest/v1/ \
|
|
-H "apikey: your-supabase-secret-key"
|
|
```
|
|
|
|
You should receive a valid response from PostgREST. Then verify that the legacy service role key still works:
|
|
|
|
```sh
|
|
curl http://<your-domain>/rest/v1/ \
|
|
-H "apikey: your-service-role-key"
|
|
```
|
|
|
|
Both should work and return the same result.
|
|
|
|
<Admonition type="note">
|
|
|
|
`/rest/v1/` is the Open API root and requires admin-level keys (`sb_secret_*` or legacy `SERVICE_ROLE_KEY`). Public keys (`sb_publishable_*` or legacy `ANON_KEY`) return `403` for this endpoint.
|
|
|
|
</Admonition>
|
|
|
|
You can also verify the public JWKS endpoint:
|
|
|
|
```sh
|
|
curl http://<your-domain>/auth/v1/.well-known/jwks.json
|
|
```
|
|
|
|
This should return the EC public key (the symmetric key is excluded). Third-party services can use this endpoint to obtain the public key and verify asymmetric user session JWTs without needing the private key.
|
|
|
|
### Environment variables configuration
|
|
|
|
New variables default to empty values in `.env.example`. When empty, the API gateway and all services operate in legacy-only mode: `sb_publishable` and `sb_secret` API keys are not configured.
|
|
|
|
| Environment variable (existing and new) | Type | Description |
|
|
| --------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `JWT_SECRET` | Symmetric secret | **Existing:** Shared secret for signing and verifying HS256 JWTs. Used by multiple services. |
|
|
| `ANON_KEY` | HS256 JWT | **Existing:** Legacy client-side API key. Embedded JWT with `role: "anon"`. |
|
|
| `SERVICE_ROLE_KEY` | HS256 JWT | **Existing:** Legacy server-side API key. Embedded JWT with `role: "service_role"`. |
|
|
| `SUPABASE_PUBLISHABLE_KEY` | Opaque | **New:** Short random key with checksum. Replaces `ANON_KEY` for **client-side** use. |
|
|
| `SUPABASE_SECRET_KEY` | Opaque | **New:** Short random key with checksum. Replaces `SERVICE_ROLE_KEY` for **server-side** use. |
|
|
| `JWT_KEYS` | JSON array | **New:** JSON array of signing JWKs containing the new asymmetric key pair and the legacy symmetric key. Used by Auth to sign tokens. |
|
|
| `JWT_JWKS` | JWKS (JSON) | **New:** Contains the new public key and the legacy symmetric key. Used by PostgREST, Realtime, Storage, and Functions. |
|
|
|
|
### Differences from the Supabase platform
|
|
|
|
- **One key per role.** Self-hosted Supabase supports a single `sb_publishable` and a single `sb_secret`. The platform allows creating multiple `sb_` keys per project.
|
|
- **No checksum validation.** The opaque keys use the same format as the platform (`sb_publishable_<random>_<checksum>`), but the API gateway does not validate the checksum. Keys are matched as opaque strings by the API gateway.
|
|
|
|
### Backward compatibility
|
|
|
|
The new authentication configuration is fully backward compatible:
|
|
|
|
- **The API Gateway accepts both key types simultaneously.** You can migrate clients incrementally - some using legacy API keys, others using the new ones.
|
|
- **JWKS includes the symmetric key.** `JWT_JWKS` contains both the EC public key (for verifying new ES256 tokens) and the legacy `JWT_SECRET` as a symmetric JWK (for verifying old HS256 tokens). Services that receive `JWT_JWKS` can verify both token types.
|
|
- **No database changes required.** The asymmetric key system operates entirely at the API gateway and service configuration layer.
|
|
|
|
<Admonition type="caution">
|
|
|
|
When `JWT_KEYS` is set, Auth will start signing new user session JWTs with the new asymmetric ES256 key pair. Make sure all services that verify tokens (PostgREST, Realtime, Storage) are configured with `JWT_JWKS` so they can verify both the new ES256 and legacy HS256 tokens.
|
|
|
|
</Admonition>
|
|
|
|
## Rotating the new API keys
|
|
|
|
If your new API keys are compromised or you want to rotate them periodically, you can regenerate `sb_publishable` and `sb_secret` without touching the asymmetric key pair:
|
|
|
|
```sh
|
|
sh utils/rotate-new-api-keys.sh --update-env
|
|
```
|
|
|
|
After rotating, restart services and update your client applications with the new keys:
|
|
|
|
```sh
|
|
sh run.sh recreate
|
|
```
|
|
|
|
<Admonition type="note">
|
|
|
|
Rotating new API keys does not invalidate existing user sessions. User session JWTs issued by Auth are unaffected because they are verified using the asymmetric key pair, which remains unchanged.
|
|
|
|
</Admonition>
|
|
|
|
## Regenerating asymmetric key pair
|
|
|
|
If the EC private key is compromised or you need to regenerate everything:
|
|
|
|
```sh
|
|
sh utils/add-new-auth-keys.sh --update-env
|
|
```
|
|
|
|
This generates a new EC P-256 key pair, new JWKS, new asymmetric JWTs, and new `sb_` API keys. After updating `.env` and restarting services:
|
|
|
|
- New user session tokens will be signed with the new EC key.
|
|
- Existing user session tokens signed with the old EC key will fail verification. Users will need to sign in again.
|
|
- Existing user session tokens signed with the legacy symmetric key (`JWT_SECRET`) will continue to work, since `JWT_SECRET` hasn't changed and is still included in the new JWKS.
|
|
|
|
<Admonition type="danger">
|
|
|
|
Regenerating asymmetric keys invalidates all ES256 user sessions. Plan a maintenance window if your users have active sessions.
|
|
|
|
</Admonition>
|
|
|
|
## How it works
|
|
|
|
Requests via `supabase-js` include two headers for every service except Edge Functions:
|
|
|
|
- `apikey` - the API key (`sb_` or legacy JWT)
|
|
- `Authorization` - when unauthenticated, the client SDK copies the API key here (`Bearer sb_publishable_xxx` or `Bearer eyJ...`). When authenticated, this contains the user session JWT minted by Auth.
|
|
|
|
For **Realtime WebSocket** connections, the API key is sent as a `?apikey=` query parameter in the upgrade URL instead of an `apikey` header.
|
|
|
|
**Storage** and **Edge Functions** also accept requests without an API key. These services handle their own authentication.
|
|
|
|
For details on how the API gateway routes and authenticates requests, see the [Envoy API Gateway guide](/docs/guides/self-hosting/self-hosted-envoy#authentication).
|
|
|
|
## Additional resources
|
|
|
|
- [API keys](/docs/guides/getting-started/api-keys) - How API keys work on the Supabase platform
|
|
- [Auth architecture](/docs/guides/auth/architecture) - How the Auth service handles authentication and token signing
|
|
- [JWT Signing Keys](/docs/guides/auth/signing-keys) - Best practices on managing keys used by Supabase Auth to create and verify JSON Web Tokens
|
|
- [JSON Web Token (JWT)](/docs/guides/auth/jwts) - How to best use JSON Web Tokens with Supabase
|
|
- [Self-hosting with Docker](/docs/guides/self-hosting/docker) - Initial setup guide, including legacy key generation
|
|
|
|
On GitHub:
|
|
|
|
- [Upcoming changes to Supabase API Keys (Discussion #29260)](https://github.com/orgs/supabase/discussions/29260)
|
|
- [Supabase Auth: Asymmetric Keys support (Discussion #29289)](https://github.com/orgs/supabase/discussions/29289)
|
|
- [Self-hosted Supabase: Envoy becomes the default API gateway (Discussion #48048)](https://github.com/orgs/supabase/discussions/48048)
|