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.
| Layer | Knows | Cannot know |
|---|---|---|
| Global auth guard or filter | The user, their role | Which record is requested |
| Route level role check | The user may call this route | Whether the record is theirs |
| Handler or service | The loaded record and its owner | Only what you did not load, so this is the right place |
Patterns that work in any framework
- 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.
- Centralize the policy. One function that answers whether this user may do this action on this record, called from every handler.
- Return the same response for missing and forbidden records where that is acceptable, to avoid confirming existence.
- 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
- Create two users, A and B, each with their own records.
- Log in as A, capture a request that reads, updates and deletes A's record.
- Replay it with B's token and A's record ID.
- Expect a refusal. Any success is a finding.
- Repeat for every route that takes an ID, including nested paths and bulk endpoints.
- 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.