This guide shows you how to integrate Clerk authentication with Supabase database using the 2025 native third-party authentication method.
Overview
Section titled “Overview”The native integration allows Supabase to directly accept Clerk-signed session tokens without requiring custom JWT templates or token generation.
Benefits
Section titled “Benefits”- ✅ No JWT template configuration needed
- ✅ Automatic token refresh handled by Supabase
- ✅ Simpler setup with fewer environment variables
- ✅ Better performance with shared session tokens
Quick Start
Section titled “Quick Start”1. Enable in Clerk Dashboard
Section titled “1. Enable in Clerk Dashboard”- Go to Integrations → Supabase
- Click “Enable Supabase Integration”
- Copy your Clerk domain (e.g.,
my-app.clerk.accounts.dev)
2. Configure in Supabase Dashboard
Section titled “2. Configure in Supabase Dashboard”- Go to Authentication → Providers
- Scroll to “Third-party Auth”
- Click “Add Provider” → Select “Clerk”
- Enter your Clerk domain
- Toggle “Enable” → Save
3. Update Environment Variables
Section titled “3. Update Environment Variables”# Clerk ConfigurationPUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxxCLERK_SECRET_KEY=sk_live_xxxxxCLERK_WEBHOOK_SECRET=whsec_xxxxx # Optional, for user sync
# Supabase ConfigurationSUPABASE_URL=https://your-project.supabase.coSUPABASE_ANON_KEY=eyJhbGci...SUPABASE_SERVICE_ROLE_KEY=eyJhbGci... # For webhooks only
# Public keys (client-side)PUBLIC_SUPABASE_URL=https://your-project.supabase.coPUBLIC_SUPABASE_ANON_KEY=eyJhbGci...4. Apply Database Migrations
Section titled “4. Apply Database Migrations”Run these migrations in your Supabase SQL Editor:
-- Copy and paste from:-- scripts/migrations/001_core_schema.sql
-- Creates:-- - users table-- - organization_memberships table-- - user_preferences table-- - Indexes and triggers-- Copy and paste from:-- scripts/migrations/002_security_policies.sql
-- Creates RLS policies for:-- - User data access-- - Organization member access-- - Admin-level accessUsage Patterns
Section titled “Usage Patterns”Server-Side (API Routes)
Section titled “Server-Side (API Routes)”Use the Clerk token from middleware to create authenticated Supabase clients:
import type { APIRoute } from 'astro'import { createServerSupabaseClient } from '#libs/supabase-native'
export const GET: APIRoute = async ({ locals }) => { const { clerkToken, userId } = locals
const supabase = createServerSupabaseClient(clerkToken)
const { data, error } = await supabase .from('users') .select('*') .eq('clerk_id', userId) .single()
return new Response(JSON.stringify(data))}Client-Side (React Components)
Section titled “Client-Side (React Components)”Use the useSupabase hook for automatic token handling:
import { useSupabase } from '#hooks/useSupabase'
export function UserProfile() { const { supabase, userId, isLoaded } = useSupabase() const [profile, setProfile] = useState(null)
useEffect(() => { if (!supabase || !userId) return
supabase .from('users') .select('*') .eq('clerk_id', userId) .single() .then(({ data }) => setProfile(data)) }, [supabase, userId])
if (!isLoaded) return <div>Loading...</div>
return <div>{profile?.full_name}</div>}Row Level Security (RLS)
Section titled “Row Level Security (RLS)”Understanding RLS Policies
Section titled “Understanding RLS Policies”Supabase RLS policies use Clerk’s JWT claims to control data access:
-- Users can only see their own profileCREATE POLICY "users_select_own" ON users FOR SELECT USING (((select auth.jwt())->>'sub')::text = clerk_id);How it works:
- Clerk JWT contains
subclaim (user ID) - Supabase extracts it via
auth.jwt()->>'sub' - Policy compares to
clerk_idcolumn - Only matching rows are returned
Common Policy Patterns
Section titled “Common Policy Patterns”User owns data
-- Users can view/edit their own dataUSING (((select auth.jwt())->>'sub')::text = clerk_id)Organization admin access
-- Org admins see all members in their orgUSING ( clerk_org_id IN ( SELECT clerk_org_id FROM organization_memberships WHERE user_id IN ( SELECT id FROM users WHERE clerk_id = ((select auth.jwt())->>'sub')::text ) AND clerk_org_role = 'org:admin' ))Service role (webhooks)
-- Bypass RLS for admin operationsUSING ((select auth.role()) = 'service_role')Troubleshooting
Section titled “Troubleshooting”JWT Verification Failed
Section titled “JWT Verification Failed”Problem: Queries return empty despite data existing.
Solution:
- ✅ Verify Clerk domain is correct in Supabase settings
- ✅ Ensure Supabase integration is enabled in Clerk dashboard
- ✅ Check JWT contains
role: "authenticated"claim
Debug:
const token = await auth().getToken()console.log('JWT Claims:', JSON.parse(atob(token.split('.')[1])))RLS Policy Denies Access
Section titled “RLS Policy Denies Access”Problem: Authenticated users can’t access their own data.
Solution:
- ✅ Verify policies use
auth.jwt()->>'sub'(notauth.uid()) - ✅ Check user exists in
userstable - ✅ Ensure
clerk_idmatches JWTsubclaim
Test in SQL Editor:
SELECT (select auth.jwt()->>'sub') as jwt_sub, clerk_id, clerk_id = (select auth.jwt()->>'sub')::text as matchesFROM users;Webhooks Not Syncing Users
Section titled “Webhooks Not Syncing Users”Problem: Users don’t appear in Supabase after signup.
Solution:
- ✅ Verify webhook secret matches
.envvalue - ✅ Check endpoint is publicly accessible
- ✅ Review webhook logs in Clerk dashboard
Local testing with ngrok:
ngrok http 4321# Update Clerk webhook to: https://abc123.ngrok.io/api/webhooks/clerkMigration from JWT Templates
Section titled “Migration from JWT Templates”If you’re using the deprecated JWT template method:
- Remove JWT template from Clerk dashboard
- Enable native Supabase integration in Clerk
- Add Clerk as third-party provider in Supabase
- Update code to remove
{ template: 'supabase' }parameter - Remove
SUPABASE_JWT_SECRETfrom environment variables
Additional Resources
Section titled “Additional Resources”Documentation
Section titled “Documentation”Project Files
Section titled “Project Files”Last Updated: 2025-10-06 | Method: Native Third-Party Auth