Security

Authenticated is not authorised

The most common real-world web vulnerability isn't injection. It's an endpoint that checks who you are and forgets to check what you may touch.

1 August 2026 3 min read Mohammad Aaquib Jawed

Authentication answers who you are. Authorization answers what you may do. Conflating them produces the most frequently exploited class of web vulnerability, and it does not require any clever payload — just an id in a URL.

The insecure direct object reference

An endpoint loads a record by id and renders it. The user is logged in, so the request is authenticated. Nothing checks that this record belongs to them.

Change the id, see someone else's invoice. There is no injection, no XSS, no bypass — the application did exactly what it was told.

The structural fix is to scope every lookup through the current user rather than looking up globally and checking afterwards. Loading from an association the user owns makes unauthorised access a RecordNotFound rather than a policy decision you might forget to write.

That difference matters because forgetting a scope is a visible bug, while forgetting a check looks like working code.

Scope the query to the actor. A check you must remember to write is a check that will eventually be missing.

Sequential ids make it trivial

Enumerable identifiers turn a targeted attack into a script. Non-sequential identifiers are not authorization — an unguessable id is still readable if you obtain it — but they remove the ability to enumerate your entire dataset, which is a meaningful reduction in blast radius.

Treat them as defence in depth on top of scoping, never as a substitute for it.

Checking in the wrong layer

Hiding a button is not authorization. The endpoint is still there and still reachable with curl.

Every check must exist server-side, at the point the action is performed. UI-level hiding is a usability feature — it stops people clicking things that would fail — and it protects nothing.

The same applies to client-side validation and to relying on an obscure route.

Mass assignment is authorization too

Strong parameters exist to stop a user setting fields you did not intend. A permitted attribute list that includes role, admin or account_id hands over privilege escalation in a form submission.

This is worth auditing specifically, because permitted lists grow over time and nobody re-reads them. A quick pass asking "could a user set this to something advantageous?" on every permitted attribute catches real problems.

Make it structural

The reliable pattern is a policy layer — an object per resource that answers whether an actor may perform an action — combined with a default of deny. Some frameworks let you fail the request when no authorization check ran at all, which converts the most common mistake from silent to loud.

Then test it: for each sensitive endpoint, a test that a different user gets denied. It is a handful of lines per endpoint and it catches the exact regression that matters.

All writing Reply by email