Clerk + Supabase RLS: Tenant Isolation
A user can sign in successfully and still see or change another tenant's data. Authentication proves who the user is; it does not tell Postgres which organization rows that user may access.
The risky shortcut is to trust an organization ID sent by the browser. A caller can change that value. Another common mistake is to use auth.uid(), which represents a Supabase Auth user UUID rather than Clerk's string user ID. Membership lookups inside RLS policies can also become recursive and slow.
This tutorial shows how to make the verified Clerk session token the root of the authorization decision:
- Clerk authenticates the user and supplies the active organization context.
- Supabase verifies the Clerk token and makes its claims available to Postgres.
- Postgres derives the user and tenant from those claims.
- Row-Level Security applies indexed, non-recursive policies to every query.
By the end, you will have a reusable schema, JWT helper functions, a controlled tenant-bootstrap function, explicit read/write policies, and tests for personal accounts, organizations, role boundaries, and cross-tenant attacks. The integration uses Clerk and Supabase's native third-party authentication—without a Clerk JWT template, a shared Supabase JWT secret, or auth.uid().
The complete runnable implementation is in the supaflow-labs/clerk-supabase-demo repository. The snippets below are intentionally small enough to study; use the repository migration and tests when building the complete example.
