Lesson 9 / 25
Policy Patterns and Testing
Teams, shared data and verification.
Membership and testing
Beyond owner-only, a common pattern is team membership: a members(team_id, user_id) table, and policies that allow access when a membership row exists. Put the lookup in a security definer helper function with a fixed search_path to avoid recursive policies on the members table itself. Read-only public data uses using (true) for select only. Test policies by signing in as different users from the client, or in SQL by setting the role and JWT claims inside a transaction; tools such as pgTAP can automate this.
Team access and a SQL test
Pattern plus a manual check.
create or replace function public.is_team_member(p_team_id bigint)
returns boolean
language sql stable security definer
set search_path = ''
as $$
select exists (
select 1 from public.members m
where m.team_id = p_team_id and m.user_id = (select auth.uid())
);
$$;
create policy "team members read docs" on public.docs
for select to authenticated
using (public.is_team_member(team_id));
-- manual test: impersonate a user inside a transaction
begin;
set local role authenticated;
set local request.jwt.claims = '{"sub": "00000000-0000-0000-0000-000000000001"}';
select count(*) from public.docs;
rollback;The dashboard SQL editor bypasses RLS by default
It runs as a privileged role, so a query working there proves nothing about what users see. Test as anon or authenticated.
Quick check: Why test policies as the authenticated role rather than in the default SQL editor session?
- Policies only exist in the client
- The SQL editor cannot run selects
- The default session is privileged and bypasses RLS
- Roles do not affect queries
Answer
The default session is privileged and bypasses RLS — Test from the user's point of view.