Authorization

Object and function access checks that keep authenticated users within their permissions.

In this article

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

Fig 1Identity and permission are separate checks
STUDY MODEL
Inspect one part at a timeAuthenticate
  1. Authenticate

    Verify identity: which user is this?

  2. 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.

Fig 2Check ownership where the object is read
STUDY MODEL
Inspect one part at a timeRoute checks
  1. Route checks

    Authentication and a broad read:books permission establish only preliminary access.

  2. Object lookup

    Fetching book id 5 without checking its owner can expose another user’s data.

  3. 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

Fig 3Avoid turning denial into an existence oracle
STUDY MODEL
Inspect one part at a timeGuess
  1. Guess

    An attacker guesses id 7.

  2. Leaking response

    Different externally visible responses can reveal whether a protected object exists.

  3. 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.

If you fetch the row, then check ownership and return **403**, you've confirmed the object exists. An attacker enumerates IDs this way. Instead, do the ownership check **inside the query**: when no row matches, return **404**. Now an attacker can't tell "doesn't exist" from "exists but not yours".

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").

Fig 4An admin URL needs an admin check
STUDY MODEL
Inspect one part at a timeHidden address
  1. Hidden address

    An unlisted /admin/invoices URL still accepts requests unless protected.

  2. Role latch

    Check the authenticated user’s permission to use admin functions.

  3. 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.

An admin endpoint that lists *all* invoices can't use `AND user_id = …` (admins see everything). It needs a **role check** at the routing layer: is this user actually an admin? ### Indirect object references → use UUIDs

Sequential IDs (1, 2, 3, …) are predictable. An attacker who sees /invoices/102 knows 101 and 103 exist, and can enumerate the whole table.

Fig 5Unpredictable identifiers do not grant permission
STUDY MODEL
Inspect one part at a timeSequential ID
  1. Sequential ID

    /invoices/102 suggests neighboring IDs 101, 103, and 104.

  2. Opaque ID

    An unpredictable identifier is harder to guess. UUID properties depend on the version and generation method.

  3. 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:

Fig 6Check both the owner and the function
STUDY MODEL
Inspect one part at a timeHorizontal
  1. Horizontal

    User A reaches user B’s data at the same privilege level. Apply owner or tenant scope at data access.

  2. Vertical

    A member reaches an admin-only function. Apply the required role or permission check.

  3. 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

  1. Centralize authorization logic: one layer every request passes through, not scattered checks that get forgotten.
  2. Default deny: if your logic doesn't explicitly allow an action, deny it. New endpoints are protected by default.
  3. 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.
  4. 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

Keep track of your reading.

Your reading progress stays in this browser.