mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs: jwt signing keys (#37027)
* docs: jwt signing keys * apply suggestion from @charislam Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> * add cryptography to spellchecker * fix word Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * remove easily Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix significantly --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
7 files changed
+407
-133
No files matched your search
@@ -718,8 +718,12 @@ export const auth = {
|
||||
{ name: 'Password Security', url: '/guides/auth/password-security' },
|
||||
{ name: 'Rate Limits', url: '/guides/auth/rate-limits' },
|
||||
{ name: 'Bot Detection (CAPTCHA)', url: '/guides/auth/auth-captcha' },
|
||||
{ name: 'JWTs', url: '/guides/auth/jwts' },
|
||||
{ name: 'JWT Fields Reference', url: '/guides/auth/jwt-fields' },
|
||||
{
|
||||
name: 'JSON Web Tokens (JWT)',
|
||||
url: '/guides/auth/jwts',
|
||||
items: [{ name: 'Claims Reference', url: '/guides/auth/jwt-fields' }],
|
||||
},
|
||||
{ name: 'JWT Signing Keys', url: '/guides/auth/signing-keys' },
|
||||
{ name: 'Row Level Security', url: '/guides/database/postgres/row-level-security' },
|
||||
{
|
||||
name: 'Column Level Security',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
id: 'jwt-fields'
|
||||
title: 'JWT Fields Reference'
|
||||
subtitle: 'Complete reference for JWT fields in Supabase'
|
||||
title: 'JWT Claims Reference'
|
||||
subtitle: 'Complete reference for claims appearing in JWTs created by Supabase Auth'
|
||||
---
|
||||
|
||||
This page provides a comprehensive reference for all JWT fields used in Supabase authentication tokens. This information is essential for server-side JWT validation and serialization, especially when implementing authentication in languages like Rust where field names like `ref` are reserved keywords.
|
||||
This page provides a comprehensive reference for all JWT claims used in Supabase authentication tokens. This information is essential for server-side JWT validation and serialization, especially when implementing authentication in languages like Rust where field names like `ref` are reserved keywords.
|
||||
|
||||
## JWT structure overview
|
||||
|
||||
@@ -46,7 +46,7 @@ These claims may be present depending on the authentication context:
|
||||
| `user_metadata` | `object` | **User Metadata** - User-specific data | `{"name": "John Doe"}` |
|
||||
| `amr` | `array` | **Authentication Methods Reference** - List of authentication methods used | `[{"method": "password", "timestamp": 1640991600}]` |
|
||||
|
||||
## Special fields
|
||||
## Special claims
|
||||
|
||||
| Field | Type | Description | Example | Context |
|
||||
| ----- | -------- | --------------------------------------------------- | ------------------------ | ----------------------------- |
|
||||
@@ -167,7 +167,7 @@ struct JwtClaims {
|
||||
role: String,
|
||||
iat: i64,
|
||||
exp: i64,
|
||||
// ... other fields
|
||||
// ... other claims
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,180 +1,245 @@
|
||||
---
|
||||
id: 'auth-jwts'
|
||||
title: 'JWTs'
|
||||
subtitle: 'JSON Web Tokens'
|
||||
tocVideo: 'v3Exg5YpJvE'
|
||||
title: 'JSON Web Token (JWT)'
|
||||
subtitle: 'Information on how best to use JSON Web Tokens with Supabase'
|
||||
---
|
||||
|
||||
A [JSON Web Token](https://jwt.io/introduction) is a type of data structure, represented as a string, that usually contains identity and authorization information about a user. It encodes information about its lifetime and is signed with a cryptographic key to make it tamper-resistant.
|
||||
|
||||
Supabase Access Tokens are JWTs. The JWT is sent along with every request to Supabase services. By verifying the token and inspecting the included claims, you can allow or deny access to resources. [Row Level Security](/docs/guides/database/postgres/row-level-security) policies are based on the information present in JWTs.
|
||||
Supabase Auth continuously issues a new JWT for each user session, for as long as the user remains signed in. Check the comprehensive guide on [Sessions](/docs/guides/sessions) to find out how you can tailor this process for your needs.
|
||||
|
||||
## Encoding and signing JWTs
|
||||
JWTs provide the foundation for [Row Level Security](/docs/guides/database/row-level-security). Each Supabase product is able to securely decode and verify the validity of a JWT it receives before using Postgres policies and roles to authorize access to the project's data.
|
||||
|
||||
JWTs are encoded and signed as follows.
|
||||
Supabase provides a comprehensive system of managing [JWT Signing Keys](/docs/guides/auth/signing-keys) used to create and verify JSON Web Tokens.
|
||||
|
||||
The JSON object starts out looking something like this:
|
||||
## Introduction
|
||||
|
||||
```js
|
||||
JWTs are strings that have the following structure:
|
||||
|
||||
```
|
||||
<header>.<payload>.<signature>
|
||||
```
|
||||
|
||||
Each part is a string of [Base64-URL](https://en.wikipedia.org/wiki/Base64#Variants_summary_table) encoded JSON, or bytes for the signature.
|
||||
|
||||
**Header**
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "0001",
|
||||
"name": "Sam Vimes",
|
||||
"iat": 1516239022,
|
||||
"exp": 1518239022
|
||||
"typ": "JWT",
|
||||
"alg": "<HS256 | ES256 | RS256>",
|
||||
"kid": "<unique key identifier>"
|
||||
}
|
||||
```
|
||||
|
||||
`sub` is the "subject", which is usually the UUID of the user. `name` is self-explanatory, and `iat` is the Unix timestamp at which the token was created. Many JWTs will also have an `exp`, which is the date at which the token is set to expire and can no longer be used. These are some of the standard fields you may find in a JWT, but you can pretty much store whatever you want in there, for example:
|
||||
Gives some basic identifying information about the string, indicating its type `typ`, the cryptographic algorithm `alg` that can be used to verify the data, and optionally the unique key identifier that should be used when verifying it.
|
||||
|
||||
```js
|
||||
**Payload**
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "0002",
|
||||
"name": "Věra Hrabánková",
|
||||
"iat": 1516239022,
|
||||
"exp": 1518239022,
|
||||
"theme": {
|
||||
"primary" : "#D80C14",
|
||||
"secondary" : "#FFFFFF"
|
||||
}
|
||||
"iss": "https://project_id.supabase.co/auth/v1",
|
||||
"exp": 12345678,
|
||||
"sub": "<user ID>",
|
||||
"role": "authenticated",
|
||||
"email": "someone@example.com",
|
||||
"phone": "+15552368"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Just note that the more data you store in your token, the longer the encoded string will be.
|
||||
Provides identifying information (called "claims") about the user (or other entity) that is represented by the token. Usually a JWT conveys information about what the user can access (then called Access Token) or who the user is (then called ID Token). You can use a [Custom Access Token Hook](/docs/guides/auth/auth-hooks/custom-access-token-hook) to add, remove or change claims present in the token. A few claims are important:
|
||||
|
||||
When we want to send the JWT to the user, we first encode the data using an algorithm such as `HS256`. There are many libraries (and several different algorithms) that can be used to do this encoding/decoding, such as [`jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken). The signing is as simple as:
|
||||
| Claim | Description |
|
||||
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `iss` | Identifies the server which issued the token. If you append `/.well-known/jwks.json` to this URL you'll get access to the public keys with which you can verify the token. |
|
||||
| `exp` | Sets a time limit after which the token should not be trusted and is considered expired, even if it is properly signed. |
|
||||
| `sub` | Means _subject_, is the unique ID of the user represented by the token. |
|
||||
| <span className="whitespace-nowrap!">`role`</span> | The Postgres role to use when applying Row Level Security policies. |
|
||||
| ... | All other claims are useful for quick access to profile information without having to query the database or send a request to the Auth server. |
|
||||
|
||||
```js
|
||||
// from https://replit.com/@awalias/jsonwebtokens#index.js
|
||||
let token = jwt.sign({ name: 'Sam Vimes' }, 'some-secret')
|
||||
```
|
||||
**Signature**
|
||||
|
||||
And the resulting string will look like this:
|
||||
A [digital signature](https://en.wikipedia.org/wiki/Digital_signature) using a [shared secret](https://en.wikipedia.org/wiki/HMAC) or [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography). The purpose of the signature is to verify the authenticity of the `<header>.<payload>` string without relying on database access, liveness or performance of the Auth server. To verify the signature avoid implementing the algorithms yourself and instead rely on `supabase.auth.getClaims()`, or other high-quality JWT verification libraries for your language.
|
||||
|
||||
```js
|
||||
eyJhbGciOiJIUzI1NiJ9
|
||||
.eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ
|
||||
.zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA
|
||||
```
|
||||
## Supabase and JWTs
|
||||
|
||||
You will notice that the string is actually made up of three components:
|
||||
Supabase creates JWTs in these cases for you:
|
||||
|
||||
The first segment `eyJhbGciOiJIUzI1NiJ9` is known as the "header", and when decoded just tells us which algorithm was used to do the encoding:
|
||||
1. When using Supabase Auth, an access token (JWT) is created for each user while they remain signed in. These are short lived, so they are continuously issued as your user interacts with Supabase APIs.
|
||||
2. As the legacy JWT-based [API keys](/docs/guides/api/api-keys) `anon` and `service_role`. These have a 10 year expiry and are signed with a shared secret, making them hard to rotate or expire. These JWTs express public access via the `anon` key, or elevated access via the `service_role` key. We strongly recommend switching to publishable and secret API keys.
|
||||
3. On-the-fly when using publishable or secret API keys. Each API key is transformed into a short-lived JWT that is then used to authorize access to your data. Accessing these short-lived tokens is generally not possible.
|
||||
|
||||
```js
|
||||
{
|
||||
"alg": "HS256"
|
||||
}
|
||||
```
|
||||
In addition to creating JWTs, Supabase can also accept JWTs from other Auth servers via the [Third-Party Auth](/docs/guides/auth/third-party/overview) feature or ones you've made yourself using the legacy JWT secret or if you've imported in [JWT Signing Key](/docs/guides/auth/signing-keys).
|
||||
|
||||
The second segment <code className="break-all">eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ</code> contains our original payload:
|
||||
## Using custom or third-party JWTs
|
||||
|
||||
```js
|
||||
{
|
||||
"sub": "0001",
|
||||
"name": "Sam Vimes",
|
||||
"iat": 1516239022,
|
||||
"exp": 1518239022
|
||||
}
|
||||
```
|
||||
<Admonition type="note">
|
||||
|
||||
The last segment `zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA` is the signature itself, which is the part used by the website or service provider to verify that a token sent by some user is legitimate. It is produced in the first instance by running the cryptographic function HS256 on the following input:
|
||||
The `supabase.auth.getClaims()` method is meant to be used only with JWTs issued by Supabase Auth. If you make your own JWTs using the legacy JWT secret or a key you've imported, the verification may fail. We strongly recommend using a JWT verification library for your language to verify this type of JWT based on the claims you're adding in them.
|
||||
|
||||
```js
|
||||
HMACSHA256(
|
||||
base64UrlEncode(header) + "." +
|
||||
base64UrlEncode(payload)
|
||||
<jwt_secret>
|
||||
)
|
||||
```
|
||||
</Admonition>
|
||||
|
||||
You can test out minting your own tokens on [https://jwt.io](https://jwt.io).
|
||||
Your Supabase project accepts a JWT in the `Authorization: Bearer <jwt>` header. If you're using the Supabase client library, it does this for you.
|
||||
|
||||
It is important to note that anyone who possesses the `jwt_secret` here can create new tokens, and also verify existing ones. More advanced JWT algorithms use two secrets: one for the creation of tokens, and a separate one to verify the validity of signed tokens.
|
||||
If you are already using Supabase Auth, when a user is signed in, their access token JWT is automatically managed and sent for you with every API call.
|
||||
|
||||
You might wonder why JWTs are so popular all of a sudden. The answer is that with the mass adoption of microservice architecture, we were in a situation where several distinct microservices (APIs, websites, servers, etc.) want to validate that a user is who they say they are, or are in other words a "logged-in" user. Traditional session tokens are no use here, since they would require each microservice to either maintain a record of currently valid session tokens or to query a central database each time a user wants to access a resource in order to check the validity of the session token – very inefficient indeed. JWT-based auth in this sense is decentralized, since anyone with the `jwt_secret` can verify a token without needing access to a centralized database.
|
||||
If you wish to send a JWT from a Third-Party Auth provider, or one you made yourself by using the legacy JWT secret or a JWT signing key you imported, you can pass it to the client library using the `accessToken` option.
|
||||
|
||||
{/* supa-mdx-lint-disable-next-line Rule004ExcludeWords */}
|
||||
<Tabs type="underlined" queryGroup="language">
|
||||
|
||||
Note: One downside of JWTs is that they are not easily voidable, unlike session tokens. If a JWT is leaked to a malicious actor, they will be able to redeem it anywhere until the expiry date is reached – unless of course the system owner updates the `jwt_secret` (which will of course invalidate _everyone's_ existing tokens).
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
## JWTs in Supabase
|
||||
```typescript
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
In Supabase we issue JWTs for three different purposes:
|
||||
|
||||
1. `anon key`: This key is used to bypass the Supabase API gateway and can be used in your client-side code.
|
||||
2. `service role key`: This key has super admin rights and can bypass your Row Level Security. Do not put it in your client-side code. Keep it private.
|
||||
3. `user specific jwts`: These are tokens we issue to users who log into your project/service/website. It's the modern equivalent of a session token, and can be used by a user to access content or permissions specific to them.
|
||||
|
||||
The first token here, the `anon key` token, is for developers to send along with their API requests whenever they want to interact with their Supabase database.
|
||||
|
||||
Let's say you want to read the names of all the rows in a table `colors`. We would make a request like:
|
||||
|
||||
```bash
|
||||
curl 'https://xscduanzzfseqszwzhcy.supabase.co/rest/v1/colors?select=name' \
|
||||
-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNDIwNTE3NCwiZXhwIjoxOTI5NzgxMTc0fQ.-NBR1WnZyQGpRLdXJfgfpszoZ0EeE6KHatJsDPLIX8c"
|
||||
```
|
||||
|
||||
If we put this token into https://jwt.io, we see it decodes to:
|
||||
|
||||
```js
|
||||
{
|
||||
"role": "anon",
|
||||
"iss": "supabase",
|
||||
"iat": 1614205174,
|
||||
"exp": 1929781174
|
||||
}
|
||||
```
|
||||
|
||||
This JWT is signed by a `jwt_secret` specific to the developer's Supabase token (you can find this secret alongside this encoded "anon key" on your Dashboard under Settings > API page) and is required to get past the Supabase API gateway and access the developer's project.
|
||||
|
||||
The idea with this particular key is that it's safe to put into your client, meaning it's okay if your end users see this key – but _only_ if you first enable Row Level Security.
|
||||
|
||||
The second key, `service role key`, should only ever be used on one of your own servers or environments, and should never be shared with end users. You might use this token to do things like make batch inserts of data.
|
||||
|
||||
The `user access token` is the JWT issued when you call for example:
|
||||
|
||||
```js
|
||||
supabase.auth.signIn({
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'They_Live_1988!',
|
||||
const supabase = createClient('https://<supabase-project>.supabase.co', 'SUPABASE_ANON_KEY', {
|
||||
accessToken: async () => {
|
||||
return '<your JWT here>'
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This token should be passed in addition to the `apikey` header as an `Authorization Bearer` header like:
|
||||
</TabPanel>
|
||||
|
||||
```bash
|
||||
curl 'https://xscduanzzfseqszwzhcy.supabase.co/rest/v1/colors?select=name' \
|
||||
-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNDIwNTE3NCwiZXhwIjoxOTI5NzgxMTc0fQ.-NBR1WnZyQGpRLdXJfgfpszoZ0EeE6KHatJsDPLIX8c" \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjE1ODI0Mzg4LCJzdWIiOiIwMzM0NzQ0YS1mMmEyLTRhYmEtOGM4YS02ZTc0OGY2MmExNzIiLCJlbWFpbCI6InNvbWVvbmVAZW1haWwuY29tIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwifSwidXNlcl9tZXRhZGF0YSI6bnVsbCwicm9sZSI6ImF1dGhlbnRpY2F0ZWQifQ.I-_oSsJamtinGxniPETBf-ezAUwDW2sY9bJIThvdX9s"
|
||||
<TabPanel id="dart" label="Flutter">
|
||||
|
||||
```dart
|
||||
await Supabase.initialize(
|
||||
url: supabaseUrl,
|
||||
anonKey: supabaseKey,
|
||||
debug: false,
|
||||
accessToken: () async {
|
||||
return "<your JWT here>";
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
You'll notice that this token is quite a bit longer, since it contains information specific to the user such as:
|
||||
</TabPanel>
|
||||
|
||||
```js
|
||||
{
|
||||
"aud": "authenticated",
|
||||
"exp": 1615824388,
|
||||
"sub": "0334744a-f2a2-4aba-8c8a-6e748f62a172",
|
||||
"email": "valid.email@supabase.io",
|
||||
"app_metadata": {
|
||||
"provider": "email"
|
||||
},
|
||||
"user_metadata": null,
|
||||
"role": "authenticated"
|
||||
<TabPanel id="swift" label="Swift (iOS)">
|
||||
|
||||
```swift
|
||||
import Supabase
|
||||
|
||||
let supabase = SupabaseClient(
|
||||
supabaseURL: URL(string: "https://<supabase-project>.supabase.co")!,
|
||||
supabaseKey: "SUPABASE_ANON_KEY",
|
||||
options: SupabaseClientOptions(
|
||||
auth: SupabaseClientOptions.AuthOptions(
|
||||
accessToken: {
|
||||
return "<your JWT here>"
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
```kotlin
|
||||
val supabase = createSupabaseClient(
|
||||
"https://<supabase-project>.supabase.co",
|
||||
"SUPABASE_ANON_KEY"
|
||||
) {
|
||||
accessToken = {
|
||||
"<your JWT here>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If using the service role key, you'll need to pass it into both the `apikey` and `authorization` headers (again, only do this from a secure environment such as your own server):
|
||||
</TabPanel>
|
||||
|
||||
```bash
|
||||
curl "$YOUR_PROJECT_URL/rest/v1/colors?select=name" \
|
||||
-H "apikey: $YOUR_SERVICE_ROLE_KEY" \
|
||||
-H "authorization: Bearer $YOUR_SERVICE_ROLE_KEY"
|
||||
</Tabs>
|
||||
|
||||
In the past there was a recommendation to set custom headers on the Supabase client with the `Authorization` header including your custom JWT. This is no longer recommended as it's less flexible and causes confusion when combined with a user session from Supabase Auth.
|
||||
|
||||
## Verifying a JWT from Supabase
|
||||
|
||||
If you're not able to use the Supabase client libraries, the following can be used to help you securely verify JWTs issued by Supabase.
|
||||
|
||||
Supabase Auth exposes a [JSON Web Key](https://datatracker.ietf.org/doc/html/rfc7517) Set URL for each Supabase project:
|
||||
|
||||
```http
|
||||
GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json
|
||||
```
|
||||
|
||||
Now that you understand what JWTs are and where they're used in Supabase, you can explore how to use them in combination with Row Level Security to start restricting access to certain tables, rows, and columns in your Postgres database.
|
||||
Which responds with JWKS object containing one or more asymmetric [JWT signing keys](/docs/guides/auth/signing-keys) (only their public keys).
|
||||
|
||||
```json
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"kid": "<match with kid from JWT header>",
|
||||
"alg": "<match with alg from JWT header>",
|
||||
"kty": "<RSA|EC|OKP>",
|
||||
"key_ops": ["verify"]
|
||||
// public key fields
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This endpoint is served directly from the Auth server, but is also additionally cached by the Supabase Edge for 10 minutes, significantly speeding up access to this data regardless of where you're performing the verification. It's important to be aware of the cache expiry time to prevent unintentionally rejecting valid user access tokens. We recommend waiting at least 20 minutes when creating a standby signing key, or revoking a previously used key.
|
||||
|
||||
Make sure that you do not cache this data for longer in your application, as it might make revocation difficult. If you do, make sure to provide a way to purge this cache when rotating signing keys to avoid unintentionally rejecting valid user access tokens.
|
||||
|
||||
Below is an example of how to use the [jose TypeScript JWT verification library](https://github.com/panva/jose) with Supabase JWTs:
|
||||
|
||||
```typescript
|
||||
import { jwtVerify, createRemoteJWKSet } from 'jose'
|
||||
|
||||
const PROJECT_JWKS = createRemoteJWKSet(
|
||||
new URL('https://project-id.supabase.co/auth/v1/.well-known/jwks.json')
|
||||
)
|
||||
|
||||
/**
|
||||
* Verifies the provided JWT against the project's JSON Web Key Set.
|
||||
*/
|
||||
async function verifyProjectJWT(jwt: string) {
|
||||
return jwtVerify(jwt, PROJECT_JWKS)
|
||||
}
|
||||
```
|
||||
|
||||
### Verifying with the legacy JWT secret or a shared secret signing key
|
||||
|
||||
If your project is still using the legacy JWT secret, or you're using a shared secret (HS256) signing key, we recommend always verifying a user access token directly with the Auth server by sending a request like so:
|
||||
|
||||
```http
|
||||
GET https://project-id.supabase.co/auth/v1/user
|
||||
apikey: publishable or anon legacy API key
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
If the server responds with HTTP 200 OK, the JWT is valid, otherwise it is not.
|
||||
|
||||
Because the Auth server runs only in your project's specified region and is not globally distributed, doing this check can be quite slow depending on where you're performing the check. Avoid doing checks like this from servers or functions running on the edge, and prefer routing to a server within the same geographical region as your project.
|
||||
|
||||
If you are using the legacy JWT secret, or you've imported your own shared secret (HS256) signing key, you may wish to verify using the shared secret. **We strongly recommend against this approach.**
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
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:
|
||||
|
||||
- Using a shared secret can make it more difficult to keep aligned with security compliance frameworks such as SOC2, PCI-DSS, ISO27000, HIPAA, etc.
|
||||
- A shared secret that is in the hands of a malicious actor can be used to impersonate your users, give them access to privileged actions or data.
|
||||
- It is difficult to detect or identify when or how a shared secret has been given to a malicious actor.
|
||||
- Consider who might have even accidental access to the shared secret: systems, staff, devices (and their disk encryption and vulnerability patch status).
|
||||
- A malicious actor can use a shared secret **far into the future**, so lacking current evidence of compromise does not mean your data is secure.
|
||||
- It can be very easy to accidentally leak the shared secret in publicly available source code such as in your website or frontend, mobile app package or other executable. This is especially true if you accidentally add the secret in environment variables prefixed with `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_` or other conventions by web frameworks.
|
||||
- Rotating shared secrets might require careful coordination to avoid downtime of your app.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Check the JWT verification libraries for your language on how to securely verify JWTs signed with the legacy JWT secret or a shared secret (HS256) signing key. We strongly recommend relying on the Auth server as described above, or switching to a different signing key based on public key cryptography (RSA, Elliptic Curves) instead.
|
||||
|
||||
## Resources
|
||||
|
||||
- JWT debugger: https://jwt.io/
|
||||
- [JWT Fields Reference](/docs/guides/auth/jwt-fields) - Complete reference for all JWT fields in Supabase
|
||||
- [JWT Signing Keys](/docs/guides/auth/signing-keys)
|
||||
- [JWT Claims Reference](/docs/guides/auth/jwt-fields) - Complete reference for all JWT claims used by Supabase Auth
|
||||
- [API keys](/docs/guides/api/api-keys)
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
id: 'auth-signing-keys'
|
||||
title: 'JWT Signing Keys'
|
||||
subtitle: 'Best practices on managing keys used by Supabase Auth to create and verify JSON Web Tokens'
|
||||
---
|
||||
|
||||
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](/docs/guides/auth/sessions) for all the details about how Auth creates tokens for a user's session. Read up on [JWTs](/docs/guides/auth/jwts) if you are not familiar with the basics.
|
||||
|
||||
## Overview
|
||||
|
||||
When a JWT is issued by Supabase Auth, the key used to create its [signature](https://en.wikipedia.org/wiki/Digital_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](/docs/reference/javascript/storage-from-createsignedurl). **No longer recommended.** Available for backward compatibility. |
|
||||
| Signing keys | Asymmetric key (RSA, Elliptic Curves) | A JWT signing key based on [public-key cryptography](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/HMAC). |
|
||||
|
||||
### Benefits of the signing keys system
|
||||
|
||||
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](/docs/guides/api/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](/docs/guides/api/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](/docs/guides/api/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](/docs/guides/database/postgres/row-level-security) has strong key revocation guarantees. |
|
||||
|
||||
## Getting started
|
||||
|
||||
You can start migrating away from the legacy JWT secret through the Supabase dashboard. This process does not cause downtime for your application.
|
||||
|
||||
1. Start off by clicking the _Migrate JWT secret_ button on the [JWT signing keys](/dashboard/project/_/settings/jwt/signing-keys) page. This step will import the existing legacy JWT secret into the new JWT signing keys system. Once this process completes, you will no longer be able to rotate the legacy JWT secret using the old system.
|
||||
2. Simultaneously, we're creating a new asymmetric JWT signing key for you to rotate to. This key starts off as standby key -- meaning it's being advertised as a key that Supabase Auth will use in the future to create JWTs.
|
||||
3. If you're not ready to switch away from the legacy JWT secret right now, you can stop here without any issue. If you wish to use a different signing key -- either to use a different signing algorithm (RSA, Elliptic Curve or shared secret) or to import a private key or shared secret you already have -- feel free to move the standby key to _Previously used_ before finally moving it to _Revoked._
|
||||
4. If you do wish to start using the standby key for all new JWT use the _Rotate keys_ button. A few important notes:
|
||||
- Make sure your app does not directly rely on the legacy JWT secret. If it's verifying every JWT against the legacy JWT secret (using a library like `jose`, `jsonwebtoken` or similar), continuing with the rotation might break those components.
|
||||
- If you're using [Edge Functions](/docs/guides/functions) that have the Verify JWT setting, continuing with the rotation might break your app. You will need to turn off this setting.
|
||||
- In both cases, change or add code to your app or Edge Function that verifies the JWT. Use the `supabase.auth.getClaims()` function or read more about [Verifying a JWT from Supabase](/docs/guides/auth/jwts#verifying-a-jwt-from-supabase) on the best way to do this.
|
||||
5. Rotating the keys immediately causes the Auth server to issue new JWT access tokens for signed in users signed with the new key. Non-expired access tokens will remain to be accepted, so no users will be forcefully signed out.
|
||||
6. Plan for revocation of the legacy JWT secret.
|
||||
- If your access token expiry time is configured to be 1 hour, wait at least 1 hour and 15 minutes before revoking the legacy JWT secret -- now under the _Previously used_ section.
|
||||
- This prevents currently active users from being forcefully signed out.
|
||||
- In some situations, such as an active security incident you may want to revoke the legacy JWT secret immediately.
|
||||
|
||||
## Rotating and revoking keys
|
||||
|
||||
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**
|
||||
|
||||
- The legacy JWT secret has been leaked in logs, committed to source control, or accidentally exposed in the frontend build of your application, a library, desktop or mobile app package, etc.
|
||||
- You suspect that a [member of your organization](/docs/guides/platform/access-control) has lost control of their devices, and a malicious actor may have accessed the JWT secret via the Supabase dashboard or by accessing your application's backend configuration.
|
||||
- You suspect that an ex-team-member of your organization may be a malicious actor, by abusing the power the legacy JWT secret provides.
|
||||
- Make sure you also switch to [publishable and secret API keys](/docs/guides/api/api-keys) and disable the `anon` and `service_role` keys.
|
||||
- If you've imported a private key, and you're suspecting that this private key has been compromised on your end similarly.
|
||||
|
||||
**Closer alignment to security best practices and compliance frameworks (SOC2, PCI-DSS, ISO27000, HIPAA, ...)**
|
||||
|
||||
- It is always prudent to rotate signing keys at least once a year.
|
||||
- Some security compliance frameworks strongly encourage or require frequent cryptographic key rotation.
|
||||
- If you're using Supabase as part of a large enterprise, this may be required by your organization's security department.
|
||||
- Creating muscle memory for the time you'll need to respond to an active security incident.
|
||||
|
||||
**Changing key algorithm for technical reasons**
|
||||
|
||||
- You may wish to switch signing algorithms due to compatibility problems or to simplify development on your end.
|
||||
|
||||
### Lifetime of a signing key
|
||||
|
||||
<div className="flex flex-row gap-6 items-center w-full">
|
||||
|
||||
<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]"
|
||||
/>
|
||||
|
||||
<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. |
|
||||
|
||||
### Public key discovery and caching
|
||||
|
||||
When your signing keys use an asymmetric algorithm based on [public-key cryptography](https://en.wikipedia.org/wiki/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:
|
||||
|
||||
```http
|
||||
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](/docs/guides/database/postgres/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:
|
||||
|
||||
- **Urgent key revocation.** If you are in a security incident where a signing key must be urgently revoked, due to the multi-level cache your application components may still trust and authenticate JWTs signed with the revoked key. Supabase products (Auth, Data API, Storage, Realtime) **do not rely on this cache and revocation is instantaneous.** Should this be an issue for you, ensure you've built a cache busting mechanism as part of your app's backend infrastructure.
|
||||
- **Quick key creation and rotation.** If you're migrating away from the legacy JWT secret or when only using the `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.
|
||||
|
||||
## Choosing the right signing algorithm
|
||||
|
||||
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](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).
|
||||
|
||||
| Algorithm | <span className="!whitespace-nowrap">JWT `alg`</span> | Information |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <span className="!whitespace-nowrap">[NIST P-256 Curve](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)</span><br/>(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](https://en.wikipedia.org/wiki/RSA_cryptosystem)</span><br/>(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](https://en.wikipedia.org/wiki/EdDSA#Ed25519)</span><br/>(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](https://en.wikipedia.org/wiki/HMAC)</span><br/>(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. |
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
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:
|
||||
|
||||
- Using a shared secret can make it more difficult to keep aligned with security compliance frameworks such as SOC2, PCI-DSS, ISO27000, HIPAA, etc.
|
||||
- A shared secret that is in the hands of a malicious actor can be used to impersonate your users, give them access to privileged actions or data.
|
||||
- It is difficult to detect or identify when or how a shared secret has been given to a malicious actor.
|
||||
- Consider who might have even accidental access to the shared secret: systems, staff, devices (and their disk encryption and vulnerability patch status).
|
||||
- A malicious actor can use a shared secret **far into the future**, so lacking current evidence of compromise does not mean your data is secure.
|
||||
- It can be very easy to accidentally leak the shared secret in publicly available source code such as in your website or frontend, mobile app package or other executable. This is especially true if you accidentally add the secret in environment variables prefixed with `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_` or other conventions by web frameworks.
|
||||
- Rotating shared secrets might require careful coordination to avoid downtime of your app.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### Why is it not possible to extract the private key or shared secret from Supabase?
|
||||
|
||||
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.
|
||||
|
||||
### How to create (mint) JWTs if access to the private key or shared secret is not possible?
|
||||
|
||||
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](/docs/reference/cli/introduction) to quickly and securely generate a private key ready for import:
|
||||
|
||||
```sh
|
||||
supabase gen generate-key ES256
|
||||
```
|
||||
|
||||
Make sure you store this private key in a secure location, as it will not be extractable from Supabase.
|
||||
|
||||
### Why is a 5 minute wait imposed when changing signing key states?
|
||||
|
||||
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.
|
||||
|
||||
### Why is deleting the legacy JWT secret disallowed?
|
||||
|
||||
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.
|
||||
|
||||
### Why does revoking the legacy JWT secret require disabling of `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.
|
||||
@@ -0,0 +1,9 @@
|
||||
stateDiagram-v2
|
||||
[*] --> standby : A new key is created and advertized
|
||||
standby --> in_use : Once all components have picked up the new key, new JWTs can be issued with it
|
||||
in_use --> previously_used : Rotation, JWT remain accepted
|
||||
previously_used --> revoked : Once all JWTs created with the previous key expire (or sooner)
|
||||
revoked --> [*] : Delete permanently after 7 days
|
||||
|
||||
revoked --> standby
|
||||
previously_used --> standby
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg aria-roledescription="stateDiagram" role="graphics-document document" viewBox="0 0 336 766" height="766" class="statediagram" xmlns="http://www.w3.org/2000/svg" width="336" id="container"><style>#container{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#container .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#container .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#container .error-icon{fill:#552222;}#container .error-text{fill:#552222;stroke:#552222;}#container .edge-thickness-normal{stroke-width:1px;}#container .edge-thickness-thick{stroke-width:3.5px;}#container .edge-pattern-solid{stroke-dasharray:0;}#container .edge-thickness-invisible{stroke-width:0;fill:none;}#container .edge-pattern-dashed{stroke-dasharray:3;}#container .edge-pattern-dotted{stroke-dasharray:2;}#container .marker{fill:#333333;stroke:#333333;}#container .marker.cross{stroke:#333333;}#container svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#container p{margin:0;}#container defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#container g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#container g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#container g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#container g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#container g.stateGroup line{stroke:#333333;stroke-width:1;}#container .transition{stroke:#333333;stroke-width:1;fill:none;}#container .stateGroup .composit{fill:white;border-bottom:1px;}#container .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#container .state-note{stroke:#aaaa33;fill:#fff5ad;}#container .state-note text{fill:black;stroke:none;font-size:10px;}#container .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#container .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#container .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#container .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#container .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#container .edgeLabel .label text{fill:#333;}#container .label div .edgeLabel{color:#333;}#container .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#container .node circle.state-start{fill:#333333;stroke:#333333;}#container .node .fork-join{fill:#333333;stroke:#333333;}#container .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#container .end-state-inner{fill:white;stroke-width:1.5;}#container .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#container .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#container #statediagram-barbEnd{fill:#333333;}#container .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#container .cluster-label,#container .nodeLabel{color:#131300;}#container .statediagram-cluster rect.outer{rx:5px;ry:5px;}#container .statediagram-state .divider{stroke:#9370DB;}#container .statediagram-state .title-state{rx:5px;ry:5px;}#container .statediagram-cluster.statediagram-cluster .inner{fill:white;}#container .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#container .statediagram-cluster .inner{rx:0;ry:0;}#container .statediagram-state rect.basic{rx:5px;ry:5px;}#container .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#container .note-edge{stroke-dasharray:5;}#container .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#container .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#container .statediagram-note text{fill:black;}#container .statediagram-note .nodeLabel{color:black;}#container .statediagram .edgeLabel{color:red;}#container #dependencyStart,#container #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#container .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#container :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><defs><marker orient="auto" markerUnits="userSpaceOnUse" markerHeight="14" markerWidth="20" refY="7" refX="19" id="container_stateDiagram-barbEnd"><path d="M 19,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><g class="root"><g class="clusters"></g><g class="edgePaths"><path marker-end="url(#container_stateDiagram-barbEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid transition" id="edge0" d="M228,22L228,30.167C228,38.333,228,54.667,228,71C228,87.333,228,103.667,228,111.833L228,120"></path><path marker-end="url(#container_stateDiagram-barbEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid transition" id="edge1" d="M202.194,160L186.495,172.167C170.796,184.333Line truncated
|
||||
|
After Width: | Height: | Size: 28 KiB |
@@ -37,6 +37,9 @@ allow_list = [
|
||||
"[Cc]ooldowns?",
|
||||
"[Cc]oroutines?",
|
||||
"[Cc]ron",
|
||||
"[Cc]rypto",
|
||||
"[Cc]ryptography",
|
||||
"[Cc]ryptosystem",
|
||||
"[Dd]atasets?",
|
||||
"[Dd]atasources?",
|
||||
"[Dd]e facto",
|
||||
@@ -48,6 +51,7 @@ allow_list = [
|
||||
"[Ee]ntrypoints?",
|
||||
"[Ee]nums?",
|
||||
"[Ee]nv",
|
||||
"[Ee]x",
|
||||
"[Ee]xecutables?",
|
||||
"[Ff]atals",
|
||||
"[Ff]rontend",
|
||||
@@ -59,6 +63,7 @@ allow_list = [
|
||||
"[Hh]ypertables?",
|
||||
"[KMG]bps",
|
||||
"[KMG]iB",
|
||||
"[Ll]iveness",
|
||||
"[Mm]atryoshka",
|
||||
"[Mm]essageBird",
|
||||
"[Mm]icroservices?",
|
||||
|
||||
Reference in new issue
Block a user