Skip to content

Authentication ​

Every authenticated request to GCM carries a JSON Web Token (JWT) signed by Supabase Auth. The token has three jobs:

  1. Identify the user (sub claim).
  2. Identify which organization they belong to (organization_id claim, set by our auth hook).
  3. 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:

bash
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:

json
{
  "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:

json
{
  "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:

bash
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.