Hiding the admin button isn't access control: protect admin routes properly
In AI-built apps, admin pages are often protected only in the UI. How to enforce admin access where the data lives.
A common AI-generated pattern: if (user.isAdmin) showAdminButton(). The page or button disappears, but the data and actions behind it are still reachable by anyone who calls the API directly from DevTools.
Where the check must live
- Supabase: in RLS policies or security-definer functions that check a role stored in a table users can't edit (or in a custom JWT claim set by the server), not in
user_metadata. - Firebase: in Security Rules using custom claims (
request.auth.token.admin == true) set with the Admin SDK. - Your own API / server actions: check the session and role on every request, inside the handler.
Example: Supabase admin-only table
create table public.admins (user_id uuid primary key references auth.users); alter table public.admins enable row level security; -- no policies: clients can't read or edit it create policy "admins read all orders" on public.orders for select to authenticated using (exists (select 1 from public.admins a where a.user_id = (select auth.uid())));
Test it
Log in as a normal user, open DevTools → Network, copy an admin request and replay it (or open /admin directly). It should fail with 401/403 or return no rows.
Find every admin-only page, action and query in this app. Enforce admin access server-side (RLS policies or server route checks using a role users cannot modify), not just by hiding UI. Then tell me how to test it as a non-admin user.
Related: RLS policy examples · Pre-launch checklist
Scan your app free
Paste your URL. ShipLatch checks for leaked keys, exposed /.env and /.git, and flags Supabase/Firebase so you know what to lock down. The free scan finds the issues; the $29 Fix Pack gives you fix prompts written for your app and re-scans that prove each fix worked.
Want the whole pre-launch process in one place? Get App Security for Vibe Coders, our $9 PDF playbook.