-- Custom Access Token Hook -- Implements AD-2 from docs/03-architecture.md: -- "A Postgres function copies role/org_id into JWT claims (custom -- access token hook) so RLS can check them cheaply." -- -- Run this AFTER schema.sql (needs the profiles table to exist). -- After running, enable the hook in the Supabase dashboard (see bottom -- of this file for the exact steps) — running the SQL alone does not -- activate it. create or replace function public.custom_access_token_hook(event jsonb) returns jsonb language plpgsql stable as $$ declare claims jsonb; user_role public.user_role; user_org_id uuid; begin -- Look up this user's role and org_id from profiles. select role, org_id into user_role, user_org_id from public.profiles where id = (event->>'user_id')::uuid; claims := event->'claims'; if user_role is not null then claims := jsonb_set(claims, '{role}', to_jsonb(user_role)); claims := jsonb_set(claims, '{org_id}', to_jsonb(user_org_id)); else claims := jsonb_set(claims, '{role}', 'null'); claims := jsonb_set(claims, '{org_id}', 'null'); end if; event := jsonb_set(event, '{claims}', claims); return event; end; $$; -- The hook runs as the supabase_auth_admin role, not as the logged-in -- user, so it needs explicit permission to call the function and read -- profiles. Everyone else is explicitly blocked from calling it directly. grant usage on schema public to supabase_auth_admin; grant execute on function public.custom_access_token_hook to supabase_auth_admin; revoke execute on function public.custom_access_token_hook from authenticated, anon, public; grant select on table public.profiles to supabase_auth_admin; create policy "Allow auth admin to read profiles for JWT hook" on public.profiles as permissive for select to supabase_auth_admin using (true); -- ============================================================ -- Dashboard steps to enable this hook (run the SQL above first): -- -- 1. Supabase dashboard → Authentication → Hooks (left sidebar, -- under "Configuration"). -- 2. Find "Customize Access Token (JWT) Claims hook". -- 3. Choose "Postgres function" as the hook type. -- 4. Select public.custom_access_token_hook from the dropdown. -- 5. Enable the hook (toggle on) and save. -- 6. Existing logged-in sessions keep their OLD token until they -- refresh/re-login — log out and back in to see new claims. -- ============================================================