BOLA (Broken Object Level Authorisation) and IDOR (Insecure Direct Object Reference) are two names for the same core flaw: an application checks that you are logged in, but not that the specific record you are asking for actually belongs to you. Change an ID in a request and you can read — or modify — someone else's data. It sits at number one in the OWASP API Security Top 10, and for good reason.
What BOLA actually looks like
Imagine an API endpoint that returns an invoice:
GET /api/invoices/1001
Authorization: Bearer eyJhbGciOi...
You are authenticated, and invoice 1001 is yours, so the API returns it. Now change one character:
GET /api/invoices/1002
If invoice 1002 belongs to another customer and the API returns it anyway, that is BOLA. The server verified who you are (authentication) but never asked whether you are allowed to see this particular object (authorisation).
The same flaw applies to writes, which are usually worse. A PUT or DELETE against someone else's object ID can let an attacker modify or destroy data across your entire customer base with a simple numbered loop.
BOLA vs IDOR: same flaw, different vocabulary
IDOR is the older, web-era term, popularised through the OWASP Top 10's broken access control category. BOLA is the name the OWASP API Security Top 10 gave the object-level variant, to distinguish it from BFLA (Broken Function Level Authorisation), where a regular user can call admin-only functions. In practice, if someone reports an IDOR in your API, they mean BOLA. The remediation is identical.
Common patterns we look for
-
Sequential integer IDs. The classic case — enumerate
/users/1through/users/10000. - Guessable references. Email addresses, usernames or order numbers used as the object key.
- UUIDs treated as authorisation. Random IDs make enumeration harder, but UUIDs leak constantly — in other API responses, exports, logs, emails and shared links. If knowing the ID is the only access control, the control is the secrecy of the ID, and that always erodes.
- Cross-tenant access. In multi-tenant SaaS, user A in company X requests an object belonging to company Y. The blast radius is your whole customer base, and this is the finding that ends up in breach notifications.
-
Secondary endpoints. The main endpoint checks ownership, but the export endpoint, the mobile API, the v1 legacy route or a nested include (
/orders/55?include=customer) does not. - IDs in request bodies and tokens. Object references do not only live in URLs — they hide in JSON bodies, headers and JWT claims that the server trusts without re-checking.
Why scanners cannot find it
Automated scanners detect vulnerabilities with signatures: an error string that indicates SQL injection, a reflected payload that indicates XSS. BOLA has no signature. The request is well-formed, the response is a perfectly normal 200 OK with valid JSON — the only thing wrong is that the data belongs to somebody else.
Deciding that requires context a tool does not have: which user should be able to access which object. That is business logic, not syntax. It requires testing with at least two accounts (ideally in two tenants), swapping object references between them, and judging whether each response was appropriate. Tools like Burp's Autorize extension help automate the request-swapping, but a human still has to build the account matrix and interpret the results. This is one of the clearest examples of the difference between a penetration test and a vulnerability scan.
How we test for BOLA
On an API penetration test we ask for at least two test accounts per role, in separate tenants where the product is multi-tenant. Then we build a full inventory of endpoints and systematically test every object reference in every method — GET, POST, PUT, PATCH and DELETE — replacing our own object IDs with the other account's. We repeat this across roles to catch vertical issues (user accessing admin objects) as well as horizontal ones (user accessing peer objects), and we check the less obvious carriers: body parameters, bulk endpoints, exports and websocket messages.
How to fix and prevent it
- Deny by default. Every object access should fail unless an explicit ownership or permission check passes.
-
Centralise the check. One authorisation layer or policy module, not ad-hoc
ifstatements scattered across handlers. Scattered checks are how endpoints get missed. -
Scope queries at the data layer.
WHERE id = ? AND tenant_id = ?makes the safe behaviour structural rather than optional. - Do not rely on unguessable IDs. Use random IDs if you like, but treat them as identifiers, never as authorisation.
- Write authorisation tests. A regression test asserting that user B receives a 403 or 404 for user A's object is cheap and catches re-introductions in CI.
FAQ
Are UUIDs enough to prevent IDOR?
No. UUIDs prevent trivial enumeration, but object IDs leak through other endpoints, exports, referral links and logs. Security through unguessable identifiers is not authorisation — every request must still verify that the caller is permitted to access that object.
Is BOLA only an API problem?
No. Traditional web applications have the same flaw — that is what IDOR originally described. APIs suffer more because object IDs are exposed directly in every request and responses are machine-readable, which makes discovery and exploitation faster.
Should a missing-authorisation check return 403 or 404?
Either can be correct. A 404 avoids confirming that the object exists, which is slightly better for privacy; a 403 is more transparent. What matters is consistency and, above all, that the data is not returned.
If you want to know whether your API leaks data across users or tenants, talk to us about an API penetration test — authorisation testing with multiple accounts is the core of our methodology, not an optional extra.
Need cybersecurity expertise?
Drop your email and we'll be in touch within one business day.