What is BOLA (IDOR)? The number one API vulnerability, with examples and how to prevent it
By Kike Gandia · Co-Founder & CEO, OSCP
BOLA (Broken Object Level Authorization) is the flaw that occurs when an API checks who you are but not whether the object you ask for is yours. Changing an identifier in the request —an invoice number, an order ID, a user ID— is enough to read, modify or delete someone else's data. It is the number one risk in the OWASP API Security Top 10 and the modern version of what for years was called IDOR (Insecure Direct Object Reference). This guide explains the difference between the two terms, what it looks like in real requests, why scanners almost never find it, how it is tested and which measures genuinely fix it.
What BOLA is and what IDOR is
An API receives an authenticated request —with a valid session, token or key— that includes an object identifier: GET /api/v1/invoices/1043. If the server checks that the token is valid but not that invoice 1043 belongs to the token's user or company, any registered user can request 1044, 1045 and all the rest. That is a BOLA.
The formal definition has three parts:
- The attacker is authenticated or the feature is public: they do not need to steal anyone's credentials, their own account is enough.
- The object is identified by a value the client controls: an ID in the path, a parameter, a body field, a header.
- The object-level authorization check is missing: the server does not verify that this user may perform this action on this specific object.
In MITRE's weakness classification it corresponds to CWE-639, “Authorization Bypass Through User-Controlled Key”, and in the 2023 OWASP API Security Top 10 it is API1:2023. The impact ranges from exposing other users' data to modifying or deleting it and, in the worst cases, taking over accounts.
BOLA vs IDOR: are they the same?
In practice they are used as synonyms, and a pentester reporting either one is talking about the same flaw. The difference lies in origin and emphasis:
| Aspect | IDOR | BOLA |
|---|---|---|
| Full name | Insecure Direct Object Reference | Broken Object Level Authorization |
| Origin | A category in early editions of the OWASP Top 10 for web applications; now part of Broken Access Control | Number one risk in the OWASP API Security Top 10 since its first edition, in 2019 |
| Focuses on | The reference: the identifier that is exposed and can be tampered with | The cause: the missing authorization check on the object |
| Usual context | Classic web applications | REST and GraphQL APIs, mobile apps and microservices |
The change of name is not cosmetic. “Insecure direct reference” invites you to think the problem is the identifier and that hiding it or making it unpredictable fixes it. “Broken object-level authorization” makes clear where the flaw is and where the fix belongs: on the server, on every access to every object.
What a BOLA looks like in real requests
The vulnerable identifier can travel in any part of the request, and it is not always a number:
| Where the identifier is | Example | What the attacker gets |
|---|---|---|
| Path | GET /api/v1/orders/58213 | Other customers' orders, addresses and phone numbers |
| Query parameter | GET /api/documents?customerId=311 | Another company's documents |
| Request body | POST /api/transfers with "sourceAccount": "GB29…" | Operations ordered on someone else's account |
| Header | X-Tenant-Id: 27 | A jump into another customer's data in a multi-tenant SaaS |
| Nested resource | GET /api/companies/12/employees/88 | Reading another company's employee because only the company in the path is checked |
| GraphQL | a query with order(id: "…") | The same as in REST, often in nested resolvers nobody reviewed |
| Files | GET /downloads/contracts/2024-117.pdf | Downloading other people's contracts or receipts |
| Write operations | PUT, PATCH or DELETE on /api/addresses/9921 | Modifying or deleting another user's data |
| Batch operations | GET /api/invoices?ids=1001,1002,1003 | Extracting hundreds of objects in a single request |
Two variants deserve special attention. Write variants, because the impact moves from confidentiality to integrity: changing another account's email usually ends in taking over that account. And multi-tenant variants, where the object belongs to a customer organisation: a single BOLA exposes every customer on the platform at once.
Why it is so common
No team decides not to check authorization. BOLA appears because of how APIs are built:
- Frameworks solve authentication, not ownership. A middleware validates the token on every route; checking that order 58213 belongs to the user depends on the business model and has to be written endpoint by endpoint.
- Testing is done with a single user. The developer tests with their own account, sees their own data and everything works. Without a second user, the flaw is invisible.
- Identifiers travel to the client. The mobile app or the SPA needs the IDs to request objects, so the attacker sees them and can change them.
- Microservices trust whoever calls them. The gateway authenticates and the internal service assumes that, if the request arrived, it is authorized for any object.
- Nested resources and new routes get forgotten. GET /orders/{id} is protected, and GET /orders/{id}/invoice is added later without the same check.
- Direct database access from the client. On backend-as-a-service platforms, if row-level security policies are not enabled or are badly written, the result is a BOLA across the whole database. It is one of the flaws AI-generated applications repeat most often.
Why scanners almost never find it
To an automated scanner, a 200 response with data is a correct response. It does not know that order 58213 belongs to another customer, because that is business logic, not a technical pattern like an injection or a missing header.
Some tools let you configure two sessions and compare responses, and they help on small, well-documented APIs. But they still need someone to define which objects belong to whom, which roles exist and what each one should see, and they fail in the most interesting cases: identifiers that only appear in responses from other endpoints, nested objects, write operations with side effects or objects legitimately shared between users.
That is why BOLA is at the same time the most frequent flaw in API penetration tests and one of the ones that most often slips past the automated analysis companies already have.
How a BOLA is tested
The test is methodical rather than sophisticated. The essential thing is to have at least two users per role, ideally in two different organisations if the application is multi-tenant, and to walk through the whole API with both:
| Step | What is done | What to look for |
|---|---|---|
| Map | Walk through the application as user A capturing every request, or start from the OpenAPI specification | Every endpoint that receives an identifier |
| Collect identifiers | Note user B's object IDs appearing in responses, shared links, exports or emails | Valid identifiers of other users' objects, even if they are not predictable |
| Swap | Replay each of A's requests replacing A's identifiers with B's, using A's token | Responses with data from, or effects on, B's objects |
| Every method | Test read, update and delete, not just GET | Write BOLAs, with higher impact |
| No session | Replay the requests without a token or with an expired one | Endpoints that should require authentication and do not |
| Compare responses | Tell apart 403, 404, empty 200 and 200 with data | Partial checks or filtering done only in the response |
Extensions for intercepting proxies that replay every request with another user's session automate much of the swapping, but interpretation is still manual: some objects are legitimately shared and some responses look correct when they are not.
The OWASP testing guide covers this procedure as WSTG-ATHZ-04, “Testing for Insecure Direct Object References”.
One detail that gets overlooked: an unpredictable identifier, such as a UUID, does not remove the BOLA. It only changes the question from “can I guess the ID?” to “can I obtain the ID?”, and the answer is usually yes: it appears in other API responses, in shared URLs, in notifications or in browser logs.
Real cases
The best-documented case of this flaw is First American Financial, a US title insurance company, because the SEC published its investigation in detail:
| Aspect | What the official source says |
|---|---|
| The flaw | Documents in its EaglePro web application were served at URLs with a sequential document number; changing the digits in the URL showed documents from other transactions |
| What was exposed | Over 800 million document images, some dating back to 2003, containing personal and financial data |
| When it was known | Its own security staff had found it months earlier, in an internal penetration test in January 2019; in May 2019 a cybersecurity journalist alerted the company and the case became public |
| SEC penalty (2021) | $487,616, not for the vulnerability itself but because the information did not reach the executives responsible for what the company disclosed to investors |
| NYDFS penalty (2023) | $1 million for violating the New York financial regulator's cybersecurity regulation |
It is a classic IDOR in a web application rather than an API, but the mechanism is exactly the same. And it leaves two lessons that go beyond the technical: the flaw was found months before it became public, so the problem was not detecting it but prioritising and fixing it; and a BOLA that exposes customer data is a leadership issue, not just a development team one.
There are large-scale API incidents whose exact mechanism is not known. In January 2023, T-Mobile told the SEC that an attacker had obtained data from some 37 million customer accounts “through a single API without authorization”, without detailing the type of flaw. That is the rule rather than the exception: official disclosures rarely explain the technical cause, which is why lists of “IDOR breaches” circulating without sources deserve scepticism.
How to fix it properly
The fix has to live on the server, on every access to every object. These measures, from most to least important:
| Measure | What it solves | What it does not solve |
|---|---|---|
| Per-object authorization check in a central layer | Ensures every read and write verifies user, object and action | Nothing, if some endpoints bypass that layer |
| Queries scoped to the user or tenant | Looking up the order by its ID and the current user's at the same time, so another user's ID simply does not exist | Objects shared between users, which need an explicit rule |
| Row-level security in the database | A second barrier even if the application forgets a filter | Complex authorization logic that does not translate well into database policies |
| Identifier-free endpoints for your own resources | GET /api/me/profile instead of GET /api/users/77 removes the tamperable parameter | Resources the user is genuinely allowed to choose |
| Unpredictable identifiers (UUIDs) | Make mass enumeration harder | Do not replace the check: the ID can leak |
| Uniform response | Returning 404 both when the object does not exist and when it is not yours avoids confirming which IDs exist | The underlying flaw, if the check is missing |
| Enumeration detection | Alerting on many requests to sequential identifiers or other users' objects | Slow attacks or ones targeting a few objects |
The design rule that prevents the most BOLAs is deny by default: if a new endpoint does not declare its access policy, it does not ship. And the one that helps most in review: give the check a searchable name in the code, instead of conditions scattered across every controller.
How to stop it coming back: testing and review
A fixed BOLA comes back easily in the next endpoint. What works to prevent that:
- Authorization tests in the pipeline. For every endpoint that receives an identifier, a test that creates two users and checks that A cannot read, modify or delete B's data. It is cheap, fast and catches the regression before production.
- Code review with a fixed question. In every pull request that adds or changes an endpoint: where is it checked that this object belongs to this user?
- Endpoint inventory. An up-to-date OpenAPI specification shows which endpoints receive identifiers and lets you check they all have their test.
- Watch BOLA's siblings. The OWASP API Top 10 has two other authorization risks: at property level (fields that should not be returned or accepted) and at function level (admin endpoints reachable by regular users). They are tested the same way, with several users and roles.
- A pentest after access model changes. New roles, shared accounts, multi-tenancy, a partner API or a new mobile app are the moments when most BOLAs appear.
FAQ
Are BOLA and IDOR the same thing?
In practice, yes: they describe the same flaw. IDOR is the classic web application name and focuses on the identifier being tampered with; BOLA is the OWASP API Security Top 10 name and focuses on the cause, the missing authorization check on the object.
Does using UUIDs instead of numeric IDs prevent BOLA?
No. It makes identifiers harder to guess, but it authorizes nothing: if the attacker obtains the UUID of someone else's object —in another API response, a shared link or a notification— the flaw is still there. It is a complementary measure, never the fix.
Why didn't our vulnerability scanner detect it?
Because detecting it requires knowing who owns each object, and that is business logic. To a scanner, a response with data is a correct response. It is found by testing with at least two users per role and swapping their identifiers on every endpoint.
How severe is a BOLA?
It depends on what data it exposes and whether it allows writes. A read BOLA on personal or financial data, or any that allows modifying or deleting other users' data or taking over accounts, is usually rated high or critical. One on low-sensitivity, non-enumerable data may be medium.
If we find a BOLA, do we have to report it to the data protection authority?
Only if there was a breach: if someone unauthorised accessed personal data, the GDPR requires notification within 72 hours unless it is unlikely to result in a risk to individuals. If the flaw is found in a pentest and the logs show nobody exploited it, there is no breach to notify, although the analysis should be documented. Without sufficient logs, that conclusion is much harder to support.
How do we prevent it in development without slowing the team down?
With a centralised authorization layer, queries that always include the current user or tenant, and an automated two-user test per endpoint in the pipeline. They are small changes if adopted early and they stop most BOLAs before they reach production.
Related service
API penetration testing with per-user and per-role authorization testing
Related content
- The OWASP API Security Top 10 explained
- JWT and OAuth security in APIs
- BOLA in GraphQL: common vulnerabilities
- IDOR and multi-tenant isolation in SaaS
- Supabase without RLS: the BOLA of AI-built apps
- SaaS application penetration testing