Browser → /auth/callback → exchange code for session → set cookies → redirect to dashboard
The callback route at app/auth/callback/route.ts exchanges the OAuth code for a Supabase session, sets the session cookies, and redirects the user to their appropriate dashboard (partner or tenant).
The auth callback route must have maxDuration set appropriately in Vercel to avoid 504 errors during OAuth token exchange. The default 10s may not be enough for slow OAuth providers.
Two server-side client constructors are available:
getSupabaseServerClient()
getSupabaseServerAdminClient()
Authenticated client scoped to the current user’s session. RLS policies are enforced.
import { getSupabaseServerClient } from '@kit/supabase/server-client';const client = getSupabaseServerClient();const { data } = await client.from('tenant_controls').select('*');// Only returns rows the user has access to via RLS
Service-role client that bypasses RLS. Use only for webhooks, cron handlers, and system operations.
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';const admin = getSupabaseServerAdminClient();// Bypasses RLS — use with caution
Never use the admin client for user-initiated requests. It bypasses all RLS policies and would expose other tenants’ data.
Cookie writes must happen on the response object, not on a detached cookieStore. Writing to cookieStore inside Route Handlers can cause deadlocks. Always use response.cookies.set().
All tenant-scoped tables enforce isolation through Row Level Security:
-- Example RLS policy on tenant_controlsCREATE POLICY "Users can view controls for their tenant" ON tenant_controls FOR SELECT USING ( tenant_id IN ( SELECT tenant_id FROM tenant_memberships WHERE user_id = auth.uid() ) OR tenant_id IN ( SELECT ptl.tenant_id FROM partner_tenant_links ptl JOIN partner_memberships pm ON pm.partner_id = ptl.partner_id WHERE pm.user_id = auth.uid() ) );
This ensures that users only see data for tenants they belong to directly or through a partner relationship.