Authentication ​
Every authenticated request to GCM carries a JSON Web Token (JWT) signed by Supabase Auth. The token has three jobs:
- Identify the user (
subclaim). - Identify which organization they belong to (
organization_idclaim, set by our auth hook). - Carry role/permission claims used by row-level security policies on the database.
Getting a token ​
As an end user (in the browser) ​
The app does this for you. When you sign in at /auth, the SDK stores the token in localStorage and refreshes it automatically.
Programmatically (for scripts, CI, integrations) ​
Use Supabase's password grant:
curl -X POST 'https://fzdacujgoluefgfbmren.supabase.co/auth/v1/token?grant_type=password' \
-H 'apikey: YOUR_ANON_KEY' \
-H 'Content-Type: application/json' \
-d '{"email":"you@church.org","password":"…"}'Response:
{
"access_token": "eyJhbGc…",
"expires_in": 3600,
"refresh_token": "vN9XzL…",
"token_type": "bearer"
}Inside the token ​
Decode the access_token (base64url of the middle segment) and you'll see:
{
"sub": "9a3b…",
"email": "you@church.org",
"role": "authenticated",
"organization_id": "de00…",
"is_platform_admin": false,
"is_superadmin": false,
"exp": 1782385294
}Don't trust these claims client-side
Anyone can craft a JWT with arbitrary claims. The database trusts only what Supabase signs. If your client code makes authz decisions, treat them as UX hints — the real check happens on the server.
Refresh ​
The access token lives an hour. When it expires, swap it for a fresh one:
curl -X POST 'https://fzdacujgoluefgfbmren.supabase.co/auth/v1/token?grant_type=refresh_token' \
-H 'apikey: YOUR_ANON_KEY' \
-d '{"refresh_token":"…"}'Service-role tokens ​
The service_role key bypasses row-level security entirely. It exists for migrations, cron jobs, and backend integrations that need to act across orgs. Never ship it to a browser or commit it to source control. We use it server-side only — in edge functions via Deno.env.get('SUPABASE_SERVICE_ROLE_KEY'), never via HTTP.
Multi-org access ​
A single auth user (sub) can belong to multiple orgs by having multiple profiles rows — one per org. The active organization is selected at login (or remembered via cookie). The auth hook injects whichever organization_id is active into the JWT, so the database scopes correctly.
Switching orgs in the UI triggers a token refresh.