Authentication answers who are you? Authorization answers what are you allowed to do? Most authorization bugs come from checking permission at the wrong layer.
Authentication vs authorization
- Authenticate
Verify identity: which user is this?
- Authorize
Check whether that identity may perform this action on this object.
Knowing which user made a request does not establish what that user may do.
How to read this model
An identity gauge beside an independent access latch. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
The trap: a false sense of security
A typical backend stack: routing → handlers → service → repository. The usual mistake is to authenticate and authorize at the routing layer, then trust that the user can reach anything downstream.
- Route checks
Authentication and a broad read:books permission establish only preliminary access.
- Object lookup
Fetching book id 5 without checking its owner can expose another user’s data.
- Owner filter
Scope the query with the authenticated context, such as AND user_id = context.user_id. Do not accept the owner from untrusted input.
An authenticated user with read:books still cannot read another user’s book.
How to read this model
A record cabinet with an owner-keyed retrieval latch. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
Just because the user passed authentication and has the read:books permission does not mean they can access every book. The check must travel all the way to the data layer.
BOLA: broken object level authorization
The scenario above has a name: BOLA. It means the system checks which user at the routing layer, but never checks which object at the data layer. An attacker writes a loop over IDs and downloads every invoice, every payment, every record.
The fix: filter by owner at the point of access:
-- wrong
SELECT * FROM invoices WHERE id = 7;
-- right
SELECT * FROM invoices WHERE id = 7 AND user_id = :context_user_id;
Don't leak via error codes
- Guess
An attacker guesses id 7.
- Leaking response
Different externally visible responses can reveal whether a protected object exists.
- Consistent denial
Use the application’s intended 403 or 404 policy consistently while preserving internal diagnostics.
Choose denial behavior deliberately so an attacker cannot infer protected object existence.
How to read this model
A request inspector with a uniform denial chute. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
BFLA: broken function level authorization
BOLA is about data. BFLA is about functions: sensitive admin operations guarded only by a hidden URL ("security through obscurity").
- Hidden address
An unlisted /admin/invoices URL still accepts requests unless protected.
- Role latch
Check the authenticated user’s permission to use admin functions.
- Object scope
Even authorized functions need appropriate object-level scope for the operation.
Hiding an endpoint is not access control.
How to read this model
A privileged control panel with a role-keyed latch. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
Sequential IDs (1, 2, 3, …) are predictable. An attacker who sees /invoices/102 knows 101 and 103 exist, and can enumerate the whole table.
- Sequential ID
/invoices/102 suggests neighboring IDs 101, 103, and 104.
- Opaque ID
An unpredictable identifier is harder to guess. UUID properties depend on the version and generation method.
- Access check
A leaked or discovered opaque ID must still fail access checks for the wrong user.
UUIDs can reduce easy enumeration; every object still needs authorization.
How to read this model
A record drawer with a numbered key and an opaque key. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
The fix: use a UUID (random, unguessable) as the primary key. It can't be enumerated. The trade-off: UUIDs index a little slower than sequential integers: worth reading up on, but for any externally-exposed ID they're the right default.
Horizontal vs vertical: the mental model
Forget the acronym soup. Every authorization bug fits one of two shapes:
- Horizontal
User A reaches user B’s data at the same privilege level. Apply owner or tenant scope at data access.
- Vertical
A member reaches an admin-only function. Apply the required role or permission check.
- Both
A valid role does not replace object ownership checks, and object ownership does not grant admin privileges.
Horizontal and vertical authorization failures require different checks.
How to read this model
Two independent access latches on a shared control console. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
Four defenses
- Centralize authorization logic: one layer every request passes through, not scattered checks that get forgotten.
- Default deny: if your logic doesn't explicitly allow an action, deny it. New endpoints are protected by default.
- Test authorization specifically: automated tests for "user A can't read user B" and "member can't reach admin", run in CI. Don't test only the happy path.
- Audit logs: log every sensitive access and every failed authorization check, so you can detect and investigate probing.
Terms: GLOSSARY · Next: xss and csrf · Up: security mindset