BOLA in API Security: Why Broken Object Level Authorization Still Matters
Authentication answers who the caller is. Authorization decides what that caller may reach. Most API access-control failures live in the gap between the two.
Broken Object Level Authorization has stayed at the top of API risk rankings for years, which is unusual for a defect with such a simple description: the API accepts an object identifier from the caller and returns the object without confirming that this caller is entitled to it.
It persists for a structural reason. Authentication is easy to centralise — a gateway, a middleware, one place to get right. Object-level authorization cannot be centralised in the same way, because it depends on the relationship between a specific caller and a specific record. That check has to happen where the object is resolved, and objects are resolved in every handler, by every team, on every endpoint.
Why APIs are particularly exposed
A server-rendered application often hides object identifiers behind navigation the user cannot easily manipulate. An API publishes them by design. Resource-oriented URLs, predictable identifiers and documented schemas are all good engineering, and all of them make object references trivial to enumerate.
- Identifiers appear in paths, query strings, request bodies, headers and nested objects
- Client applications are given the identifiers they need, and often several they do not
- Documentation describes the exact shape of every request
- Mobile and single-page clients move authorization decisions to code the user controls
- Microservice boundaries create internal callers that are trusted because they are internal
That last point deserves emphasis. A gateway that authenticates external traffic and then forwards requests to internal services over a trusted network has authenticated the edge, not the object access. If the internal service resolves objects by identifier without re-deriving entitlement, the authorization boundary is at the wrong layer.
Common architectural causes
| Cause | What it looks like in code |
|---|---|
| Trusting the supplied identifier | The handler queries by the ID in the request, not by the caller's ownership |
| Client-side authorization | The UI hides the control; the endpoint does not enforce it |
| Inconsistent enforcement | Read is checked, update is not; v2 is checked, v1 is not |
| Ownership assumed by route | Nesting implies ownership without verifying the parent relationship |
| Role checks standing in for object checks | Caller has the right role, but not for that specific record |
| Bulk and export endpoints | Filters applied after retrieval rather than in the query |
The pattern across all six is the same: the query describes the object, not the relationship. An authorization-safe query is one where the caller's identity is part of the predicate, so an unentitled request returns nothing rather than returning something that must then be filtered.
A testing methodology
Object-level authorization testing is systematic rather than clever. The method matters more than any individual request.
- Enumerate object references. Walk the API surface and record every place an identifier is accepted — paths, bodies, filters, headers, nested relations, and identifiers echoed in responses.
- Establish ownership boundaries. For each object type, determine who is supposed to reach it and on what basis: ownership, tenancy, role, delegation, or an explicit share.
- Map roles and tenants. Provision at least two accounts in different tenants and, where the model allows, two roles within one tenant. Comparative testing needs a control.
- Compare authorized against unauthorized. Perform each operation as the entitled account, then replay it as the unentitled one. The difference in response is the finding.
- Verify server-side enforcement. Confirm the check exists at the API, not only in the client. Test the endpoint directly rather than through the interface.
- Cover the full verb set. Read access is usually checked first and best. Update, delete, export, share and state-changing actions are where enforcement diverges.
The researcher's perspective
Researchers who find these consistently are not guessing identifiers faster than everyone else. They are reading the application's object model and looking for the places where a relationship is implied rather than enforced.
Productive questions: which objects have more than one legitimate accessor, and how is that relationship stored? Where does the application move from one service to another, and what identity crosses that boundary? Which endpoints were added late — bulk export, admin tooling, integrations, mobile-only routes — and did they inherit the same checks as the original CRUD surface?
Defensive controls
- Centralise the decision, not the check. One authorization service can answer "may this subject perform this action on this object?", but every handler must ask it.
- Derive, do not accept. Resolve the object from the caller's context wherever possible instead of from a supplied identifier.
- Deny by default. A new endpoint with no explicit policy should fail closed, not open.
- Filter in the query. Scope by tenant and owner in the database predicate, not in post-processing.
- Make identifiers unguessable as defence in depth — never as the control itself.
- Test authorization in CI. Contract tests that assert an unentitled caller receives a denial catch regressions that code review will not.
- Log denials with subject, object and action so enumeration is visible in telemetry.
Conclusion
BOLA is not a sophisticated attack and it is not going away, because the defect is organisational as much as technical: the check has to be repeated correctly in hundreds of places by many people over several years. The teams that get it right make it structural — a policy layer every handler must call, queries scoped by identity, and automated tests that fail when a new endpoint forgets.
References
- OWASP API Security Top 10 — Broken Object Level Authorization
- OWASP API Security Project
- OWASP Authorization Cheat Sheet
Bring this to your own environment.
If any of the above describes a system you are responsible for, the fastest next step is a conversation about scope.
