← Blog

Your /admin page is protected in the UI. That's not protection.

One of the most common bugs in AI-built apps looks like security and isn’t. The app hides the admin link unless you’re an admin, redirects you to /login if you’re not signed in, greys out the buttons you’re not allowed to press. It feels locked down. It isn’t — because all of that happens in the browser, and the browser belongs to whoever’s using it.

The illusion

Client-side auth is a UX feature, not a security control. A redirect like “if no session, send to /login” runs in JavaScript, after the page and often its data have already loaded. Hiding a component just means it isn’t rendered — the code, and frequently the data, are still there. Anyone can open DevTools, disable the redirect, or simply call your API directly with curl. None of your UI runs on an attacker’s machine unless they let it.

Why it fails, concretely

Your /admin page calls an endpoint like GET /api/users to list everyone. If that endpoint doesn’t check the caller’s session and role itself, then:

  • Visiting /admin after deleting the redirect in DevTools → works.
  • Calling /api/users directly, no browser involved → works.
  • A second, logged-in non-admin user hitting the same endpoint → works.

The UI was never the lock. The endpoint was supposed to be, and isn’t.

Where the check actually has to live

Protection belongs on the server, at two layers:

  1. Every endpoint authenticates and authorizes. The route handler or server function checks who is calling (the session) and whether they’re allowed (role or ownership) before it does anything — independently, on every request.
  2. The data layer enforces it too. On Supabase or Postgres, row-level security is the backstop that holds even if an endpoint forgets. Defense shouldn’t depend on a single place being perfect.

The UI redirect stays — it’s good UX. It just doesn’t count as security.

How to test it in 30 seconds

Open a terminal and hit your protected endpoint with no session at all:

curl https://your-app.com/api/admin/users

If you get data back instead of a 401 or 403, the route is open. Then try it as a normal logged-in user (with their session token). If they get admin data, your authorization is missing too — not just authentication.

On the common stacks

  • Next.js: auth in middleware.ts alone is brittle — matchers misconfigure, and route handlers and server actions run with full privileges regardless of the UI. Check each one. (more on Next.js)
  • Supabase, Lovable, Bolt: generated apps frequently gate the UI while the database stays reachable with the public anon key. RLS is the real gate, not the screen. (more on Supabase)

“Protected in the UI” is the kind of bug that looks fine in every demo and fails the first time someone curious opens DevTools. It’s one of the first things a senior engineer checks in a seniorgrade audit — not “does the UI hide it,” but “is the endpoint actually closed.” You can sanity-check your own app with the free self-check first.