Everyday Programmer
Field Notes

Frontend Authorization Is Not Security

Hiding a button is UI, not authorization. The frontend communicates permissions — only the server can enforce them.

2 min readfield notes, security, frontend, architecture
Illustration for "Frontend Authorization Is Not Security" — Everyday Programmer

"Only admins see that button."

Yes — and anyone with browser devtools, a curl prompt, or ten minutes of patience can press it anyway. Hiding a button is a user experience decision. It is not authorization. The frontend communicates permissions; only the server can enforce them.

Everything a browser holds is in the user's hands: the JavaScript, the state, the flags your SPA set to render that admin panel. Security that depends on any of it staying hidden is not security — it's etiquette.

The rule

For every sensitive request, the server asks: who is this, and are they allowed to do this — right now? The answer never comes from the client.

Where enforcement belongs: the BFF boundary

auth-flow.txt
text

The backend-for-frontend pattern earns its name here: the browser talks only to your BFF, the BFF holds protected credentials and calls internal services, and every mutation passes a permission check sized to its risk.

Permissions change mid-session

The check that matters most is the one teams skip: now. Access gets revoked mid-meeting. Roles change. Trials expire. A permission set cached at login is a lie with a countdown on it.

Practical mitigations:

  • Short-lived tokens with server-side validation on refresh
  • Permission updates pushed over SSE/WebSocket so a revoked admin sees their UI change without a refresh — and the server has already stopped believing them regardless
  • Per-request checks, not per-route-bundle checks: the endpoint that exports data checks export permission on every export call

Audit logs are part of the feature

When an action touches money, data, or other people's records, the authorization decision itself should be logged: who, what, when, from where, allowed or denied. Denied attempts are signals — a burst of them from one account is either a bug or an incident in progress.

The frontend's job is to make the allowed path obvious and the denied path invisible. The server's job is to make the denied path impossible. Confusing the two is how "admin-only" features end up in bug bounties.

Keep reading

Related articles