apps/docs/content/guides/auth/signing-keys.mdx
Supabase Auth continuously issues a new JWT for each user session, for as long as the user remains signed in. JWT signing keys provide fine grained control over this important process for the security of your application.
Before continuing check the comprehensive guide on Sessions for all the details about how Auth creates tokens for a user's session. Read up on JWTs if you are not familiar with the basics.
When a JWT is issued by Supabase Auth, the key used to create its signature is known as the signing key. Supabase provides two systems for dealing with signing keys: the Legacy system based on the JWT secret, and the new Signing keys system.
| System | Type | Description |
|---|---|---|
| Legacy | JWT secret | Initially Supabase was designed to use a single shared secret key to sign all JWTs. This includes <span className="!whitespace-nowrap">the anon and service_role</span> keys, all user access tokens including some Storage pre-signed URLs. No longer recommended. Available for backward compatibility. |
| Signing keys | Asymmetric key (RSA, Elliptic Curves) | A JWT signing key based on public-key cryptography (RSA, Elliptic Curves) that follows industry best practices and significantly improves the security, reliability and performance of your applications. |
| Signing keys | Shared secret key | A JWT signing key based on a shared secret. |
We've designed the Signing keys system to address many problems the legacy system had. It goes hand-in-hand with the publishable and secret API keys.
| Benefit | Legacy JWT secret | JWT signing keys |
|---|---|---|
| Performance | Increased app latency as JWT validation is done by Auth server. | If using asymmetric signing key, JWT validation is fast and does not involve Auth server. |
| Reliability | To ensure secure revocation, Auth server is in the hot path of your application. | If using asymmetric signing key, JWT validation is local and fast and does not involve Auth server. |
| Security | Requires changing of your application's backend components to fully revoke a compromised secret. | If using asymmetric signing key, revocation is automatic via the key discovery endpoint. |
| Zero-downtime rotation | Downtime, sometimes being significant. Requires careful coordination with API keys. | No downtime, as each rotation step is independent and reversible. |
| Users signed out during rotation | Currently active users get immediately signed out. | No users get signed out. |
| Independence from API keys | anon and service_role must be rotated simultaneously. | Publishable and secret API keys no longer are based on the JWT signing key and can be independently managed. |
| Security compliance frameworks (SOC2, etc.) | Difficult to remain aligned as the secret can be extracted from Supabase. | Easier alignment as the private key or shared secret can't be extracted. Row Level Security has strong key revocation guarantees. |
You can start migrating away from the legacy JWT secret through the Supabase dashboard. This process does not cause downtime for your application.
jose, jsonwebtoken or similar), continuing with the rotation might break those components.supabase.auth.getClaims() function or read more about Verifying a JWT from Supabase on the best way to do this.Key rotation and revocation are one of the most important processes for maintaining the security of your project and applications. The signing keys system allows you to efficiently execute these without causing downtime of your app, a deficiency present in the legacy system. Below are some common reasons when and why you should consider key rotation and revocation.
Malicious actors abusing the legacy JWT secret, or imported private key
anon and service_role keys.Closer alignment to security best practices and compliance frameworks (SOC2, PCI-DSS, ISO27000, HIPAA, ...)
Changing key algorithm for technical reasons
<Image alt="Diagram showing the state transitions of a signing key" src={{ light: '/docs/img/guides/auth-signing-keys/states.svg', dark: '/docs/img/guides/auth-signing-keys/states.svg', }} containerClassName="max-w-[300px] min-w-[180px]" width={336} height={766} />
<div>A newly created key starts off as standby, before being rotated into in use (becoming the current key) while the existing current key becomes previously used.
At any point you can move a key from the previously used or revoked states back to being a standby key, and rotate to it. This gives you the confidence to revert back to an older key if you identify problems with the rotation, such as forgetting to update a component of your application that is relying on a specific key (for example, the legacy JWT secret).
Each action on a key is reversible (except permanent deletion).
</div> </div>| Action | Accepted JWT signatures | Description |
|---|---|---|
| <span className="!whitespace-nowrap">Create a new key</span> | Current key only, new key has not created any JWTs yet. | When you initially create a key, after choosing the signing algorithm or importing a private key you already have, it starts out in the standby state. If using an asymmetric key (RSA, Elliptic Curve) its public key will be available in the discovery endpoint. Supabase Auth does not use this key to create new JWTs. |
| <span className="!whitespace-nowrap">Rotate keys</span> | Both keys in the rotation. | Rotation only changes the key used by Supabase Auth to create new JWTs, but the trust relationship with both keys remains. |
| <span className="!whitespace-nowrap">Revoke key</span> | <span className="!whitespace-nowrap">Only from the current key.</span> | Once all regularly valid JWTs have expired (or sooner) revoke the previously used key to revoke trust in it. |
| <span className="!whitespace-nowrap">Move to standby</span> from revoked | Current and previously revoked key. | If you've made a mistake or need more time to adjust your application, you can move a revoked key to standby. Follow up with a rotation to ensure Auth starts using the originally revoked key again to make new JWTs. |
| <span className="!whitespace-nowrap">Move to standby</span> from previously used | Both keys. | This only prepares the key from the last rotation to be used by Auth to make new JWTs with it. |
| <span className="!whitespace-nowrap">Delete key</span> | - | Permanently destroys the private key or shared secret of a key, so it will not be possible to re-use or rotate again into it. |
When your signing keys use an asymmetric algorithm based on public-key cryptography Supabase Auth exposes the public key in the JSON Web Key Set discovery endpoint, for anyone to see. This is an important security feature allowing you to rotate and revoke keys without needing to deploy new versions of your app's backend infrastructure.
Access the currently trusted signing keys at the following endpoint:
GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json
Note that this is secure as public keys are irreversible and can only be used to verify the signature of JSON Web Tokens, but not create new ones.
This discovery endpoint is cached by Supabase's edge servers for 10 minutes. Furthermore the Supabase client libraries may cache the keys in memory for an additional 10 minutes. Your application may be using different caching behavior if you're not relying only on the Supabase client library.
This multi-level cache is a trade-off allowing fast JWT verification without placing the Auth server in the hot path of your application, increasing its reliability and performance.
Importantly Supabase products do not rely on this cache, so stronger security guarantees are provided especially when keys are revoked. If your application only uses Row Level Security policies and does not have any other backend components (such as APIs, Edge Functions, servers, etc.) key rotation and revocation are instantaneous.
Finally this multi-level cache is cleared every 20 minutes, or longer if you have a custom setup. Consider the following problems that may arise due to it:
supabase.auth.getClaims() method this case is handled for you automatically. If you're verifying JWTs on your own, without the help of the Supabase client library, ensure that all caches in your app have picked up the newly created standby key before proceeding to rotation.To strike the right balance between performance, security and ease-of-use, JWT signing keys are based on capabilities available in the Web Crypto API.
| Algorithm | <span className="!whitespace-nowrap">JWT alg</span> | Information |
|---|---|---|
| <span className="!whitespace-nowrap">NIST P-256 Curve</span> | ||
| (Asymmetric) | <span className="!whitespace-nowrap">ES256</span> | Elliptic Curves are a faster alternative than RSA, while providing comparable security. Especially important for Auth use cases is the fact that signatures using the P-256 curve are significantly shorter than those created by RSA, which reduces data transfer sizes and helps in managing cookie size. Web Crypto and most other cryptography libraries and runtimes support this curve. |
| <span className="!whitespace-nowrap">RSA 2048</span> | ||
| (Asymmetric) | <span className="!whitespace-nowrap">RS256</span> | RSA is the oldest and most widely supported public-key cryptosystem in use. While being easy to code by hand, it can be significantly slower than elliptic curves in certain aspects. We recommend using the P-256 elliptic curve instead. |
| <span className="!whitespace-nowrap">Ed25519 Curve</span> | ||
| (Asymmetric) | <span className="!whitespace-nowrap">EdDSA</span> | Coming soon. This algorithm is based on a different elliptic curve cryptosystem developed in the open, unlike the P-256 curve. Web Crypto or other crypto libraries may not support it in all runtimes, making it difficult to work with. |
| <span className="!whitespace-nowrap">HMAC with shared secret</span> | ||
| (Symmetric) | <span className="!whitespace-nowrap">HS256</span> | Not recommended for production applications. A shared secret uses a message authentication code to verify the authenticity of a JSON Web Token. This requires that both the creator of the JWT (Auth) and the system verifying the JWT know the secret. As there is no public key counterpart, revoking this key might require deploying changes to your app's backend infrastructure. |
There is almost no benefit from using a JWT signed with a shared secret. Although it's computationally more efficient and verification is simpler to code by hand, using this approach can expose your project's data to significant security vulnerabilities or weaknesses.
Consider the following:
NEXT_PUBLIC_, VITE_, PUBLIC_ or other conventions by web frameworks.You can only extract the legacy JWT secret. Once you've moved to using the JWT signing keys feature extracting of the private key or shared secret from Supabase is not possible. This ensures that no one in your organization is able to impersonate your users or gain privileged access to your project's data.
This guarantee provides your application with close alignment with security compliance frameworks (SOC2, PCI-DSS, ISO27000, HIPAA) and security best practices.
If you wish to make your own JWTs or have access to the private key or shared secret used by Supabase, you can create a new JWT signing key by importing a private key or setting a shared secret yourself.
Use the Supabase CLI to quickly and securely generate a private key ready for import:
supabase gen signing-key --algorithm ES256
Make sure you store this private key in a secure location, as it will not be extractable from Supabase.
To import the generated private key to your project, create a new standby key from the dashboard:
{
"kty": "EC",
"kid": "3a18cfe2-7226-43b0-bbb4-7c5242f2406e",
"d": "RDbwqThwtGP4WnvACvO_0nL0oMMSmMFSYMPosprlAog",
"crv": "P-256",
"x": "gyLVvp9dyEgylYH7nR2E2qdQ_-9Pv5i1tk7c2qZD4Nk",
"y": "CD9RfYOTyjR5U-PC9UDlsthRpc7vAQQQ2FTt8UsX0fY"
}
Once imported, click Rotate key to activate your new signing key. Any JWT signed by your old key will continue to be usable until your old signing key is manually revoked.
To mint a new JWT using the asymmetric signing key, you need to set the following JWT headers to match your generated private key.
{
"alg": "ES256",
"kid": "3a18cfe2-7226-43b0-bbb4-7c5242f2406e",
"typ": "JWT"
}
The kid header is used to identify your public key for verification. You must use the same value when importing on platform.
In addition, you need to provide the following custom claims as the JWT payload.
{
"sub": "ef0493c9-3582-425f-a362-aef909588df7",
"role": "authenticated",
"exp": 1757749466
}
sub is an optional UUID that uniquely identifies a user you want to impersonate in auth.users table.role must be set to an existing Postgres role in your database, such as anon, authenticated, or service_role.exp must be set to a timestamp in the future (seconds since 1970) when this token expires. Prefer shorter-lived tokens.For simplicity, use the following CLI command to generate tokens with the desired header and payload.
supabase gen bearer-jwt --role authenticated --sub ef0493c9-3582-425f-a362-aef909588df7
Finally, you can use your newly minted JWT by setting the Authorization: Bearer <JWT> header to all Data API requests.
A separate apikey header is required to access your project's APIs. This can be a publishable, secret or the legacy anon or service_role keys. Using your minted JWT is not possible in this header.
Changing a JWT signing key's state sets off many changes inside the Supabase platform. To ensure a consistent setup, most actions that change the state of a JWT signing key are throttled for approximately 5 minutes.
This is to ensure you have the ability, should you need it, to go back to the legacy JWT secret. In the future this capability will be allowed from the dashboard.
anon and service_role API keys?Unfortunately anon and service_role are not just API keys, but are also valid JSON Web Tokens, signed by the legacy JWT secret. Revoking the legacy JWT secret means that your application no longer trusts any JWT signed with it. Therefore before you revoke the legacy JWT secret, you must disable the anon and service_role to ensure a consistent security setup.