Tokens and claims
What the access token carries, how to check it, and what it is not for.
The access token
A JWT for your backend. A decoded example, with made-up values:
{
"iss": "https://example-project.supabase.co/auth/v1",
"sub": "3f0c9a52-7d1e-4b8a-9c6f-2e5d8b1a4c70",
"aud": "authenticated",
"iat": 1792682619,
"exp": 1792686219,
"client_id": "9d4b7c1e-3a2f-4e5d-8b6a-7c0f1e2d3a4b",
"scope": "openid",
"tenant_id": "c2a8e6f4-1b3d-4f5a-9e7c-0d2b4f6a8c1e",
"frontend_role": "member",
"email": "anna.berger@pannoniqa.example",
"given_name": "Anna",
"family_name": "Berger",
"role": "frontend_user",
"user_metadata": {},
"phone": ""
}
Read these claims:
| Claim | Meaning |
|---|---|
sub | The frontend user, stable for the life of the account. Key your users by it. The same person at two organisations has two unrelated subs. |
tenant_id | The organisation the user belongs to. |
client_id | The frontend the token was issued to. |
scope | Always openid. |
frontend_role | authenticated or member (see Frontend role). Only on these OAuth tokens. |
email | The user's real, verified address, lower-cased. It can change; never key a user by it. |
given_name, family_name | The names the user gave at sign-up. |
iss, aud, exp, iat | Standard JWT claims. aud is authenticated for every frontend's token. |
Never read the others:
roleis the database role (frontend_user), not the frontend role.user_metadatais always{}andphonealways"".app_metadata,session_id,aal,amrandis_anonymousare the Auth server's own. Take the organisation from the top-leveltenant_id.
Check every access token before you trust it
- Verify the signature against the keys of
<auth>/.well-known/jwks.json. Cache them, and fetch again for an unknownkid. Accept only the algorithm of the key in JWKS; neverHS256ornone. - Check
issagainst the issuer in discovery,exp, andaudauthenticated. - Check that
client_idis your own client ID.audis the same for every frontend, so onlyclient_idbinds a token, and itsfrontend_role, to your frontend. Skip this and your backend accepts a token another frontend of the organisation obtained, with that frontend'smember, which may mean another community. - Check that
tenant_idis your organisation. - Only then read
frontend_role,sub,emailand the names.
What the access token is not for
- Never send it to the Studio as a bearer token. The Studio refuses any
Authorization: Bearerwith an access token, and the Members API authenticates your backend by its frontend key. The one exception isGET /api/v1/members/me, which takes the token beside the key, in its own header (see The signed-in member). - Never send it to the project's database, storage or GraphQL APIs. It carries the database role
frontend_user, which has no access to anything there. - Never call
GET <auth>/user(or any other<auth>/userendpoint) with it: it answers with the account's internal technical address, not the real email. Apart from/oauth/userinfo, never send it to<auth>at all. - Keep it on your server. Never hand the access or refresh token to the browser.
ID token and UserInfo
Both carry only sub about the user, because the scope is openid alone. The ID token also carries the standard iss, aud, exp and iat, and your nonce if you sent one. Everything else is in the access token.