Walking into the manager’s office
Watch it happen
Mallory is a regular customer. She writes “role: admin” on a slip, the teller believes it, and the admin office opens.
Mallory is a regular customer with a regular badge. She walks in and can do customer things, and that is fine.
Mallory says: Hi, I’m Mallory.In bank terms and app terms
| In the bank | In your app |
|---|---|
Are you exposed?
Your app decides who is an admin by a value the browser can send (a flag, a cookie, a header). Admin pages are hidden in the menu, but the /admin API routes behind them do not check the role. A user can update their own row in a profiles or roles table, including the role column.
Read the user’s role on the server for each request and require "admin" on every admin route. Block users from editing role-like columns: keep roles in a table users cannot write, or use RLS plus column privileges or a trigger so only admins or the server can change them. Keep admin-only tables and functions behind policies, not behind a hidden URL.
What it looks like in code
// GOOD: Check role on every admin endpoint
export async function GET(request: Request) {
const user = await getAuthUser(request)
if (!user || user.role !== "admin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
}
// ... admin logic
}
// GOOD: Use Supabase RLS for role-based access
CREATE POLICY "Admins only" ON admin_settings
FOR ALL USING (
auth.uid() IN (SELECT id FROM profiles WHERE role = 'admin')
);
Ask your AI tool to fix it
Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.
Audit how this app decides who is an admin. Find every place a role, isAdmin or plan value comes from the request, a cookie or a client-writable column. Replace those with a server-side lookup of the verified user’s role, add a role check to every admin route, and make sure users cannot update their own role (for example with Supabase RLS policies, column privileges or a server-only roles table).Where this comes from
The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.
- A01:2025 Broken Access ControlOWASPActing as admin while logged in as a user is privilege elevation; deny by default
- API5:2023 Broken Function Level AuthorizationOWASPDeny by default; admin functions must check the caller's role
- Authorization Cheat SheetOWASPLeast privilege; validate permissions on every request, never client-side
- How to think about data security in Next.jsNext.jsRe-verify admin status on the server; never trust a client-sent isAdmin flag
- Column Level SecuritySupabaseRLS covers rows, not columns; use column privileges to block editing role columns
- Custom Claims & Role-based Access Control (RBAC)SupabaseKeep roles in a separate table that client roles cannot write
Spot a mistake or something out of date? Report a correction · How these guides are made and checked
The free, open-source scanner checks for most of the problems in these guides.