How do you implement Row Level Security in PostgreSQL and Supabase?
Row Level Security (RLS) enforces data access control at the database level, ensuring users can only access rows they are authorized to see regardless of the application query. Enabling: ALTER TABLE tablename ENABLE ROW LEVEL SECURITY. After enabling, no rows are accessible until policies are created. Policy anatomy: CREATE POLICY policyname ON tablename FOR operation TO role USING (condition) WITH CHECK (condition). USING controls which existing rows can be seen/modified, WITH CHECK controls which new/updated rows are allowed. Operations: SELECT (USING only), INSERT (WITH CHECK only), UPDATE (USING to filter visible rows AND WITH CHECK to validate new values), DELETE (USING only). Never use FOR ALL; create separate policies per operation for clarity. Common patterns: user owns data (USING auth.uid() = userid), team-based access (USING EXISTS (SELECT 1 FROM teammembers WHERE teamid = resource.teamid AND userid = auth.uid())), role-based access (USING auth.jwt()-'appmetadata'-'role' = 'admin'), and public read with authenticated write (SELECT policy without auth check, INSERT/UPDATE/DELETE with auth check). Supabase-specific: auth.uid() returns the authenticated user's ID from the JWT, auth.jwt() returns the full JWT for claims inspection, and policies automatically apply to all queries including those from the Supabase client library. Performance: add indexes on columns used in RLS policies (userid, teamid), avoid complex subqueries in policies (use materialized data or JWT claims instead), and test query performance with RLS enabled. Testing: verify authenticated users can only access their own data, verify unauthenticated requests are blocked, test cross-tenant access prevention, and verify policy behavior with different JWT claims. Common mistakes: using USING (true) which defeats the purpose, forgetting to enable RLS after creating policies, not creating policies for all operations, and complex policies that degrade query performance.