Users sign in smoothly; every resource is protected by deny-by-default permissions enforced below the application layer.
Recommended stack
- Supabase Auth (or Clerk) - identity. Email, OAuth, and session refresh handled; you get a verifiable JWT.
- Row-Level Security - authorization. The JWT's claims drive policies - the API can be wrong and data stays safe.
- A roles table (org_members) - RBAC. owner/admin/member per tenant, checked by definer functions in policies.
Build steps
- Map resources × verbs × roles in a table before writing any policy.
- Implement deny-by-default: enable RLS everywhere, add policies per verb, test anonymously first.
- Keep privileged mutations (billing, role changes) in server code with explicit role checks AND database policies.
- Handle session refresh on the client; treat 401 as 'refresh then retry once', not logout.
- Audit-log sensitive actions (role grants, key creation) with actor, target, and timestamp.
Watch out for
- Authorization checks only in the UI (hidden button ≠ protected endpoint).
- Long-lived tokens in localStorage for sensitive apps - prefer httpOnly cookies.
- Role checks by email string scattered through code instead of one helper.
Definition of done
- curl with a member token cannot perform admin mutations
- Privilege change takes effect on next request without redeploy
- Security review can read the whole permission model in one file