
Using Clerk with Supabase? Don’t make the rookie RLS mistakes I made
If you’re using Clerk with Supabase, you may have asked:
- Why does
auth.uid()returnNULLwhen I send a Clerk token? - Should I use the service-role key to make RLS errors disappear?
- How should Clerk Organizations map to tenants in Postgres?
- How to handle a Clerk profile without an Organization?
- How do I test RLS policies directly in SQL?
- Why can RLS make a query over a tiny table take several seconds?
- Can I use Supabase RLS without using Supabase Auth?
I kept finding variations of these questions while working with Clerk and Supabase.
The confusing part is that many answers mix three different integration paths:
- Supabase Auth examples built around
auth.uid() - The older Clerk JWT-template integration
- Clerk and Supabase’s current native third-party authentication
For the current native integration, passing the token is surprisingly simple:
const supabase = createClient(url, publishableKey, {
accessToken: async () => session?.getToken() ?? null,
})
But building a secure multi-tenant authorization model around that token requires more care.
Some mistakes are particularly easy to make:
- Using
auth.uid()even though Clerk user IDs are strings such asuser_.... - Trusting an organization ID supplied by the browser.
- Reaching for the service-role key, which bypasses RLS instead of fixing it.
- Querying a membership table recursively from its own RLS policies.
- Checking reads but forgetting
WITH CHECKprotections for inserts and updates. - Running JWT and membership functions once per row instead of hoisting them.
- Testing only successful requests and never proving that another tenant is rejected.
With Clerk, the verified user and active organization context are available through the JWT:
(select auth.jwt()->>'sub')
(select auth.jwt()->'o'->>'id')
(select auth.jwt()->'o'->>'rol')
Postgres should derive the user and tenant from these verified claims.
The browser can send a Clerk session token and filter a query for performance, but it should never be the authority that decides which tenant owns a row.
I put together a step-by-step tutorial for anyone starting with Clerk and wanting to build a multi-tenant application using Supabase RLS:
Clerk and Supabase RLS Tenant Isolation
There is also a complete runnable repository:
It includes:
- Native Clerk session tokens without JWT templates
- Personal and organization tenants
- Clerk organization-role handling
- Non-recursive authorization closures
- Explicit
SELECT,INSERT,UPDATE, andDELETEpolicies - Database-derived audit fields
- Cross-tenant foreign-key protection
- SQL contract and behavioral tests
- A manual browser security matrix
- Request-flow and data-model diagrams
My goal was to provide more than another isolated RLS snippet. The tutorial connects token delivery, claim extraction, tenant modeling, policy design, performance, and adversarial testing in one working example.
HTH fellow users just starting out on their Supabase & Clerk Journey