Safeguard
Security Guides•

Where does the object level authorization check belong in a framework API?

Route guards and global auth do not stop BOLA. The check belongs where the record is loaded. What OWASP says, and how to test it with two users.

Safeguard Security Team
4 min read

The object level check belongs in the code that loads a record for the request, not in a route guard or global filter. Those layers know who the user is, but not which record the request names. Check ownership or permission where the record is fetched, and test with two different users.

What the risk is

OWASP's first API risk, broken object level authorization, describes attackers changing an object ID in a request. The identifier can be a sequential integer, a UUID or a string, and it can appear in the path, query string, headers or body. The server often trusts the client supplied ID without checking that the caller may act on that object.

OWASP also says that comparing the session user's ID with the ID parameter is insufficient, because it covers only a small set of cases.

Why global auth does not solve it

A guard, dependency or security filter answers one question: is this request authenticated, and does the role allow this route? It runs before the handler loads anything. It cannot know that order 4821 belongs to another account. That fact only exists once the record is read.

LayerKnowsCannot know
Global auth guard or filterThe user, their roleWhich record is requested
Route level role checkThe user may call this routeWhether the record is theirs
Handler or serviceThe loaded record and its ownerOnly what you did not load, so this is the right place

Patterns that work in any framework

  1. Scope queries by owner. Load records through the user's own collection, or add the owner to the query, so another user's record is simply not found.
  2. Centralize the policy. One function that answers whether this user may do this action on this record, called from every handler.
  3. Return the same response for missing and forbidden records where that is acceptable, to avoid confirming existence.
  4. Do not rely on unguessable IDs alone. OWASP recommends random, unpredictable identifiers as one measure alongside real authorization checks, not instead of them.

Steps to test

  1. Create two users, A and B, each with their own records.
  2. Log in as A, capture a request that reads, updates and deletes A's record.
  3. Replay it with B's token and A's record ID.
  4. Expect a refusal. Any success is a finding.
  5. Repeat for every route that takes an ID, including nested paths and bulk endpoints.
  6. Put these checks in automated tests and, per OWASP, do not deploy changes that cause them to fail.

Where scanners fit

An API scanner can replay requests across two accounts, which is why it asks for two sets of credentials. It cannot design your policy for you. If your description lists every ID bearing route, a scan can exercise them all, which is one more reason to keep the OpenAPI description accurate. Related: Are Spring, FastAPI, NestJS and Rails open or closed by default.

Try it yourself

Click through it at your own pace, or open it full screen. Look for tests that vary the identifier in a request.

Safeguard's reachability analysis and dependency scanning help you focus on vulnerable code paths, and a free scan is available.

Quick check

Do UUIDs prevent BOLA?

No. They make guessing harder, but OWASP lists them alongside authorization checks, not as a replacement.

Can a role check stop BOLA?

Not alone. Two users with the same role can still reach each other's records without an ownership check.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.