mirror of
https://github.com/supabase/supabase.git
synced 2026-05-12 21:29:28 +08:00
280 lines
15 KiB
Plaintext
280 lines
15 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 [new 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
|
|
|
|
{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
|
|
|
|
- Ensure OpenSSL and Node.js 16+ are available on the machine where you will generate new keys
|
|
- Complete the [Docker setup guide](/docs/guides/self-hosting/docker), including running `generate-keys.sh` so that `JWT_SECRET`, `ANON_KEY`, and `SERVICE_ROLE_KEY` are set in your `.env` file
|
|
- If you are upgrading an existing self-hosted Supabase environment, make sure to check the [changelog](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md) and add/update 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/kong-entrypoint.sh`
|
|
- `volumes/api/kong.yml`
|
|
|
|
## 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`. Without `--update-env`, the script prints the values and prompts you 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>
|
|
|
|
After updating `.env`, enable new authentication by uncommenting these lines in `docker-compose.yml`:
|
|
|
|
```yaml name=docker-compose.yml
|
|
auth:
|
|
environment:
|
|
# JSON array of signing JWKs (EC private + legacy symmetric)
|
|
GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}
|
|
|
|
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":[]}}
|
|
```
|
|
|
|
PostgREST does not need uncommenting - it already uses `PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}` which automatically picks up `JWT_JWKS` when set.
|
|
|
|
<Admonition type="caution">
|
|
|
|
Podman does not support nested variable interpolation (`${A:-${B}}`). If you are using Podman, 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
|
|
docker compose down && docker compose up -d
|
|
```
|
|
|
|
### New API keys format
|
|
|
|
The new API keys use the [same format](/docs/guides/getting-started/api-keys) as 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 publishable key:
|
|
|
|
```sh
|
|
curl http://<your-domain>/rest/v1/ \
|
|
-H "apikey: your-supabase-publishable-key"
|
|
```
|
|
|
|
You should receive a valid response from PostgREST. Then verify that the legacy key still works:
|
|
|
|
```sh
|
|
curl http://<your-domain>/rest/v1/ \
|
|
-H "apikey: your-anon-key"
|
|
```
|
|
|
|
Both should work and return the same result.
|
|
|
|
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, and Storage to verify tokens. |
|
|
|
|
### 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
|
|
docker compose down && docker compose up -d
|
|
```
|
|
|
|
<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
|
|
|
|
Below are a few notes on the details of the new authentication architecture.
|
|
|
|
### What client SDK sends
|
|
|
|
Every request via `supabase-js` includes two headers:
|
|
|
|
- `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** accept requests without an API key. These services handle their own authentication.
|
|
|
|
### Kong API gateway routing
|
|
|
|
Kong is configured with two consumers that each accept both the legacy and new API keys:
|
|
|
|
```yaml name=volumes/api/kong.yml
|
|
consumers:
|
|
- username: anon
|
|
keyauth_credentials:
|
|
- key: $SUPABASE_ANON_KEY # legacy HS256 JWT (ANON_KEY)
|
|
- key: $SUPABASE_PUBLISHABLE_KEY # new opaque key (omitted when not configured)
|
|
- username: service_role
|
|
keyauth_credentials:
|
|
- key: $SUPABASE_SERVICE_KEY # legacy HS256 JWT (SERVICE_ROLE_KEY)
|
|
- key: $SUPABASE_SECRET_KEY # new opaque key (omitted when not configured)
|
|
```
|
|
|
|
When new API keys have not been added yet, the `kong-entrypoint.sh` script removes the empty credential entries before Kong loads the config.
|
|
|
|
To assist with the authorization flows a specialized configuration in `kong.yml` substitutes internal, gateway-level-only pre-signed JWTs for `sb_publishable` and `sb_secret` API keys. These pre-signed JWTs are also auto-configured in `.env` but **should not** be used in any application code.
|
|
|
|
| Route | Service | API key required | Header substitution |
|
|
| -------------------- | -------------------- | ---------------- | ------------------- |
|
|
| `/auth/v1/*` | Auth | Yes | `Authorization` |
|
|
| `/rest/v1/*` | PostgREST | Yes | `Authorization` |
|
|
| `/graphql/v1` | PostgREST | Yes | `Authorization` |
|
|
| `/realtime/v1/api/*` | Realtime (REST) | Yes | `Authorization` |
|
|
| `/realtime/v1/*` | Realtime (WebSocket) | Yes | `x-api-key` |
|
|
| `/storage/v1/*` | Storage | No | `Authorization` |
|
|
| `/functions/v1/*` | Edge Functions | No | - |
|
|
|
|
### Request flows
|
|
|
|
The API gateway (Kong) configuration has the logic to decide what `Authorization` header the upstream service, such as Auth, receives. The logic handles two cases: requests that only carry an API key (no user session), and requests that carry a user session JWT.
|
|
|
|
#### Unauthenticated requests (API key only, no user session JWT)
|
|
|
|
When the client sends only an `apikey` header with the API key (no `Authorization` header), or also the API key duplicated in `Authorization` by `supabase-js`:
|
|
|
|
1. The client sends `apikey: sb_publishable_xxx` (or legacy `apikey: eyJ...`).
|
|
2. The API gateway checks the key and identifies the consumer (`anon` or `service_role`).
|
|
3. The API gateway inspects the `Authorization` header. Since it is either absent or starts with `Bearer sb_` (an opaque key, not a session JWT), the plugin replaces it:
|
|
- **The new `sb_` key:** `Authorization` header is set to the internal pre-signed ES256 JWT that corresponds to the role.
|
|
- **The Legacy JWT key:** `Authorization` header is set to the legacy HS256 JWT (the `apikey` value is copied as-is).
|
|
4. The upstream service receives a valid JWT in `Authorization` and verifies it using `JWT_JWKS` (or `JWT_SECRET`).
|
|
|
|
#### Authenticated requests (user session JWT)
|
|
|
|
When the client has previously signed in through Auth and has a valid user session JWT token:
|
|
|
|
1. The client sends `Authorization: Bearer eyJ...` (a JWT session token from Auth) alongside `apikey: sb_publishable_xxx` (or legacy `apikey: eyJ...`).
|
|
2. The API gateway checks the API key and identifies the consumer.
|
|
3. The API gateway inspects the `Authorization` header. Since it exists and does **not** start with `Bearer sb_` (it's a real JWT, not an `sb_` API key), the plugin **passes it through unchanged**. This works the same way regardless of whether the `apikey` is a new `sb_` key or a legacy JWT - the gateway only looks at the `Authorization` header to decide whether a user session is present.
|
|
4. The upstream service verifies the session JWT. If Auth signed it with ES256 (when `JWT_KEYS` is configured), verification uses the EC public key. If Auth signed it with HS256 (legacy), verification uses the symmetric key. Both keys are available in `JWT_JWKS`.
|
|
|
|
The `request-transformer` expression in `kong.yml` implements this as a single Lua conditional:
|
|
|
|
```lua
|
|
-- Pseudocode for the Authorization header logic:
|
|
if authorization exists AND does not start with "Bearer sb_" then
|
|
-- User session JWT: pass through unchanged
|
|
keep authorization
|
|
elseif apikey matches secret key then
|
|
-- Replace with pre-signed service_role ES256 JWT
|
|
set authorization = "Bearer <service_role ES256 JWT>"
|
|
elseif apikey matches publishable key then
|
|
-- Replace with pre-signed anon ES256 JWT
|
|
set authorization = "Bearer <anon ES256 JWT>"
|
|
else
|
|
-- Legacy JWT key: copy apikey as authorization
|
|
set authorization = apikey
|
|
end
|
|
```
|
|
|
|
## Additional resources
|
|
|
|
- [Understanding 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)
|