new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · Auth · all subjects

authentication/jwts

114 notes in this subject, read out of this brain and free to use. This is page 2 of 2.

Asymmetric JWT signing requirement for OAuth

For OAuth implementations, asymmetric signing algorithms (RS256 or ES256) are strongly recommended instead of default HS256. With asymmetric keys, third-party clients can validate JWTs using the public key from JWKS endpoint without needing access to JWT secret. This is more secure and follows OAuth best practices. ID tokens specifically require asymmetric signing algorithms - ID token generation will fail if project uses HS256.

JWT token verification with Node.js example

Example Node.js code for verifying OAuth access tokens using jose library: import { createRemoteJWKSet, jwtVerify } from 'jose' const JWKS = createRemoteJWKSet( new URL('https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json') ) async function verifyAccessToken(token) { try { const { payload } = await jwtVerify(token, JWKS, { issuer: 'https://<project-ref>.supabase.co/auth/v1', audience: 'authenticated', }) return payload } catch (error) { console.error('Token verification failed:', error) return null } }

JWT token verification with Python example

Example Python code for verifying OAuth access tokens using python-jose library: from jose import jwt from jose.backends import RSAKey import requests jwks = requests.get('https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json').json() def verify_access_token(token): try: payload = jwt.decode( token, jwks, algorithms=['RS256'], issuer='https://<project-ref>.supabase.co/auth/v1', audience='authenticated' ) return payload except jwt.JWTError as e: print(f'Token verification failed: {e}') return None

JWT token verification with Go example

Example Go code for verifying OAuth access tokens using go-oidc library: package main import ( "context" "github.com/coreos/go-oidc/v3/oidc" ) func verifyAccessToken(ctx context.Context, token string) (*oidc.IDToken, error) { provider, err := oidc.NewProvider( ctx, "https://<project-ref>.supabase.co/auth/v1", ) if err != nil { return nil, err } verifier := provider.Verifier(&oidc.Config{ ClientID: "authenticated", }) return verifier.Verify(ctx, token) }

Verify session validity with getUser() not getClaims()

The only way to ensure a user has logged out or their session has ended is to get the user's details with getUser(). The getClaims() method only checks local JWT validation (signature and expiration) but does not verify with the auth server whether the session is still valid or if the user has logged out server-side.

Access token session_id claim

Every access token contains a session_id claim, a UUID, uniquely identifying the session of the user. You can correlate this ID with the primary key of the auth.sessions table.

Recommended JWT expiration time

Most applications should use the default expiration time of 1 hour. You can customize this value in the Auth settings > Sessions. Setting a value over 1 hour is generally discouraged for security reasons.

Reasons to avoid JWT expiration below 5 minutes

Values below 5 minutes, and especially below 2 minutes, should not be used in most situations because: the shorter the expiration time, the more frequently refresh tokens are used which increases Auth server load; servers can be out of sync for tens of seconds while user devices can be off by minutes or hours causing clock skew errors; Supabase client libraries try to refresh the session ahead of time which won't be possible with too short expiration; access tokens should be valid for at least as long as the longest running request in your application to avoid token expiration midway through processing.

Access Tokens remain valid until expiry after sign out

Access Tokens of revoked sessions remain valid until their expiry time, which is encoded in the exp claim. The user will not be immediately logged out and will only be logged out when the Access Token expires.

JWT Signing Keys Overview

Supabase Auth issues a new JWT for each user session as long as the user remains signed in. JWT signing keys provide fine-grained control over the process of creating and verifying these tokens. There are two systems for dealing with signing keys: the Legacy system based on a single JWT secret, and the new Signing keys system based on public-key cryptography or shared secrets.

Legacy JWT Secret System

The legacy JWT secret system uses a single shared secret key to sign all JWTs, including the anon and service_role keys, and all user access tokens including some Storage pre-signed URLs. This approach is no longer recommended but remains available for backward compatibility.

Asymmetric Signing Keys System

The signing keys system supports asymmetric keys based on public-key cryptography (RSA, Elliptic Curves) that follow industry best practices and significantly improve the security, reliability, and performance of applications compared to the legacy system.

Shared Secret Signing Keys

The signing keys system supports shared secret keys based on HMAC as a symmetric option, though this is not recommended for production applications as it requires both the JWT creator and verifier to know the secret, and revocation might require deploying changes to application backend infrastructure.

JWT Signing Keys System Benefits over Legacy

The signing keys system provides the following benefits over the legacy JWT secret: faster JWT validation without Auth server involvement when using asymmetric keys, improved reliability as validation is local and fast, automatic revocation via key discovery endpoint, zero-downtime key rotation, no forceful sign-out of users during rotation, independence of API keys from JWT signing keys, and stronger security compliance alignment as the private key or shared secret cannot be extracted.

Migrating from Legacy JWT Secret

Migration from the legacy JWT secret to signing keys has no downtime. Click the Migrate JWT Secret button on the JWT signing keys page, which imports the existing legacy JWT secret into the new system and creates a new asymmetric JWT signing key as a standby key. The standby key can be rotated into use when ready.

Key Rotation Requirements Before Rotating

Before rotating keys, ensure your app does not directly rely on the legacy JWT secret by verifying JWTs against it using libraries like jose or jsonwebtoken. If using Edge Functions with the Verify JWT setting enabled, you must turn off this setting before rotation. Update or add code to verify JWTs using supabase.auth.getClaims() or by reading the JWT verification guide.

JWT Access Token Rotation Behavior

When rotating keys, Supabase Auth immediately issues new JWT access tokens for signed-in users signed with the new key. Non-expired access tokens continue to be accepted, so no users are forcefully signed out.

Revocation Timing to Prevent Sign-outs

When revoking the legacy JWT secret, wait at least 1 hour and 15 minutes after rotation if your access token expiry time is 1 hour, before revoking it from the Previously used section. This prevents currently active users from being forcefully signed out. In active security incidents, immediate revocation may be warranted.

Key Rotation Lifecycle States

A newly created signing key starts as standby (not used by Auth yet, public key in discovery endpoint). It can be rotated to become the current key (actively used by Auth). The previously current key becomes previously used. Keys can move between standby, previously used, and revoked states. All actions except permanent deletion are reversible.

Key Rotation Accepted JWT Signatures

When rotating keys, both the old and new keys are accepted for JWT verification. When a key is revoked, only the current key's signatures are accepted. When moving a revoked key back to standby and rotating, both the current and previously revoked key signatures are accepted.

Public Key Discovery Endpoint

When using asymmetric signing algorithms, Supabase Auth exposes the public key in the JSON Web Key Set discovery endpoint at GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json. Public keys are irreversible and can only verify signatures, not create them.

Public Key Discovery Endpoint Caching

The discovery endpoint is cached by Supabase edge servers for 10 minutes. Supabase client libraries may cache keys in memory for an additional 10 minutes. Applications using different caching behavior may have different cache durations. The multi-level cache is cleared every 20 minutes or longer with custom setup.

Supabase Products Don't Rely on Key Discovery Cache

Supabase products (Auth, Data API, Storage, Realtime) do not rely on the key discovery cache, so key rotation and revocation are instantaneous for these components. If an application only uses Row Level Security policies and has no other backend components, key rotation and revocation are instantaneous.

Urgent Key Revocation Security Consideration

During a security incident requiring urgent key revocation, application components using the multi-level cache may still trust and authenticate JWTs signed with the revoked key. Supabase products revoke instantaneously. Applications should implement a cache busting mechanism for their own backend infrastructure to address urgent revocation scenarios.

NIST P-256 Elliptic Curve Signing Algorithm

The NIST P-256 Curve is an asymmetric algorithm with JWT alg value ES256. Elliptic curves are faster than RSA while providing comparable security. P-256 signatures are significantly shorter than RSA signatures, reducing data transfer sizes and helping manage cookie size. Web Crypto and most cryptography libraries support this curve. Recommended as the default choice.

RSA 2048 Signing Algorithm

RSA 2048 is an asymmetric algorithm with JWT alg value RS256. RSA is the oldest and most widely supported public-key cryptosystem. It can be significantly slower than elliptic curves in certain aspects. Supabase recommends using P-256 elliptic curve instead.

EdDSA Ed25519 Signing Algorithm (Coming Soon)

Ed25519 Curve is an asymmetric algorithm with JWT alg value EdDSA, based on a different elliptic curve cryptosystem developed openly. It is coming soon but Web Crypto and other crypto libraries may not support it in all runtimes, making it difficult to work with currently.

HMAC with Shared Secret Signing Algorithm

HMAC with shared secret is a symmetric algorithm with JWT alg value HS256. Not recommended for production applications. It uses a message authentication code to verify JWT authenticity and requires both JWT creator and verifier to know the secret. Has no public key counterpart, and revoking may require deploying backend changes.

Reasons to Avoid Shared Secret Signing Keys

Shared secret signing keys pose significant security vulnerabilities: difficult to maintain security compliance frameworks alignment (SOC2, PCI-DSS, ISO27000, HIPAA), can be used by malicious actors to impersonate users or give privileged access, difficult to detect when compromised, requires considering access across systems and staff, can be misused far into the future, easy to accidentally leak in source code or frontend, and rotation may require careful coordination to avoid downtime.

Private Key and Shared Secret Extraction Restriction

Once migrated from the legacy JWT secret to signing keys, private keys and shared secrets cannot be extracted from Supabase. Only the legacy JWT secret can be extracted. This ensures no one in the organization can impersonate users or gain privileged access to project data, providing alignment with security compliance frameworks (SOC2, PCI-DSS, ISO27000, HIPAA).

Creating Custom JWTs with Signing Keys

To create custom JWTs or access the private key/shared secret, create a new signing key by importing a private key or setting a shared secret yourself. Use the Supabase CLI to generate a private key: supabase gen signing-key --algorithm ES256. Store the generated private key securely as it will not be extractable from Supabase.

Custom JWT Import Format

To import a generated private key to your project, create a new standby key from the dashboard using JSON format with fields: kty (key type), kid (key ID UUID), d (private key component), crv (curve), x and y (public key coordinates). Example shows an EC key with P-256 curve. The kid value must match when importing on platform.

Custom JWT Header Requirements

When minting a new JWT using an asymmetric signing key, set JWT headers to: alg (algorithm from generated key, e.g., ES256), kid (key ID matching the imported key), and typ (JWT). The kid header is used to identify the public key for verification.

Custom JWT Payload Claims

Custom JWT payloads must include: sub (optional UUID uniquely identifying a user in auth.users table), role (must be an existing Postgres role like anon, authenticated, or service_role), and exp (timestamp in future seconds since 1970 when token expires). Prefer shorter-lived tokens.

Generating Bearer Tokens via Supabase CLI

Use the Supabase CLI command to generate tokens with desired header and payload: supabase gen bearer-jwt --role authenticated --sub ef0493c9-3582-425f-a362-aef909588df7. Use the generated JWT by setting Authorization: Bearer <JWT> header to all Data API requests.

API Key Header Required Separately from JWT

A separate apikey header is required to access project APIs alongside the custom JWT. The apikey can be a publishable, secret, or legacy anon or service_role key. Using a custom minted JWT in the apikey header is not possible.

Signing Key State Change Throttling

Changing a JWT signing key's state is throttled for approximately 5 minutes to ensure consistent setup across the Supabase platform. Most actions that change key state are subject to this 5-minute throttle.

Legacy JWT Secret Deletion Restriction

Deleting the legacy JWT secret is disallowed to ensure you have the ability to go back to it if needed. This capability may be allowed from the dashboard in the future.

Revoking Legacy JWT Secret with API Keys

Before revoking the legacy JWT secret, you must disable the anon and service_role API keys. This is because anon and service_role are not only API keys but also valid JSON Web Tokens signed by the legacy JWT secret. Revoking the secret means the application no longer trusts JWTs signed with it, requiring API key disabling for consistent security.

JWT-based Service Role Key Rotation Alternative

If using JWT-based anon key in mobile, desktop, or CLI applications and needing to rotate a service_role JWT secret, substitute the service_role JWT-based key with a new secret key created in Settings > API Keys. This prevents downtime for your application.

Why Anon and Service Role JWT Keys Are Not Recommended

JWT-based anon and service_role keys have significant drawbacks compared to publishable and secret keys: tight coupling between JWT secret, roles, and authentication tokens preventing independent rotation; inability to roll-back unnecessary rotations; mobile app review delays make forced rotation impractical; old app versions cause rotation impossibility; 10-year JWT expiry gives malicious actors more time; self-referential and redundant JWT information; large size makes verification and manipulation difficult; signed with symmetric secret creating security risks.

Backward Compatibility with Old API Keys

You can continue using old anon and service_role API keys after enabling publishable and secret keys. This allows zero-downtime transition by gradually swapping clients while both key sets are active. Use the last used indicators on the API Keys dashboard page to confirm old keys are no longer used before deactivating.

Deactivating JWT-based API Keys

Deactivate anon and service_role JWT-based API keys in Settings > API Keys section of the Dashboard. Use the last used indicators to confirm these are no longer in use before deactivating to prevent downtime. Keys can be re-activated if needed.

API Gateway Implementation of Publishable and Secret Keys

On the hosted Supabase platform, when applications use Supabase APIs they go through the API Gateway component. The API Gateway verifies the API key sent in the apikey request header (or WebSocket query param) against the project's publishable and secret key list. If matched, it mints a temporary short-lived JWT that is forwarded to the project's servers.

Self-hosted Publishable and Secret Key Implementation

Self-hosted Supabase can replicate API Gateway behavior using programmable proxies such as Kong, Envoy, NGINX or similar to verify API keys and mint temporary short-lived JWTs.

Cognito role claim requirement for Supabase

Supabase inspects the 'role' claim in JWTs to assign the correct Postgres role when using the Data API, Storage, or Realtime. By default, Amazon Cognito JWTs do not contain a 'role' claim, which results in the 'anon' role being assigned instead of the 'authenticated' role.

How third-party auth works with Supabase APIs

Third-party auth support works by making Supabase APIs trust JWTs issued by the third-party provider in the same way they trust JWTs issued by Supabase Auth. This is possible when providers use asymmetrically signed JWTs, allowing Supabase APIs to verify but not create JWTs.

Third-party auth JWT requirement: asymmetric signing

Third-party providers must use asymmetrically signed JWTs, exposed as an OIDC Issuer Discovery URL. The signed JWTs must have a 'kid' header parameter to identify which key must be used. Symmetrically signed JWTs are not supported.

Why Supabase requires role: authenticated claim in JWT

Supabase inspects the role claim present in all JWTs sent to it to assign the correct Postgres role when using the Data API, Storage, or Realtime authorization.

Access tokens remain valid until exp claim passes

Deleting a user cannot retroactively invalidate an access token that was already issued. Supabase access tokens are stateless JWTs, so a token already in the user's hands stays valid until its exp claim passes, and during that window the account can still call the API.

Mitigate token validity window after user deletion

After deleting a user, there are two ways to handle the window where existing access tokens remain valid: (1) Keep the access token (JWT) expiry short so outstanding tokens expire soon after deletion, or (2) Validate the session_id claim against the auth.sessions table on sensitive operations, which will fail because deleting the user removes the session row.

Custom claims in JWT via Auth Hooks

Use a Custom Access Token Auth Hook to add custom claims to a user's JWT before it is issued. The hook runs before token issuance and allows you to edit the JWT to add additional attributes like user_role, plan, user_level, and other custom fields.

PL/pgSQL Auth Hook function for custom claims

Create a PL/pgSQL function named custom_access_token_hook that takes a jsonb event parameter and returns jsonb. The function fetches the user role from the user_roles table using event->>'user_id', then uses jsonb_set to add the user_role claim to event->'claims' before returning the modified event. Grant usage on public schema to supabase_auth_admin, grant execute on the function to supabase_auth_admin only, and revoke execute from authenticated, anon, and public roles. Grant all permissions on the user_roles table to supabase_auth_admin and revoke from authenticated, anon, and public. Create a permissive select policy allowing supabase_auth_admin to read user_roles.

getClaims method for JWT validation

The getClaims method validates the local JWT before showing the signed-in user in your React application.

Give your agent this brain