IDOR and Broken Object Level Authorization in REST APIs
Authorization checks that stop at authentication let attackers access objects they shouldn't own.

IDOR and BOLA are the same bug wearing two different name tags. IDOR is the older term, from the OWASP Web Top 10, and it describes the symptom: an object reference sitting in plain view, ready for someone to mess with. BOLA is the newer term, from the OWASP API Security Top 10, and it names what's actually broken underneath, which is that nobody checked whether this user had any business touching that object. Same root cause, different emphasis. The shift in language matters, though, because "authorization failure" tells an engineer exactly where to go looking, while "exposed reference" just tells them something's visible and leaves them guessing at why.
Here's the distinction that runs through everything below. Authentication asks "is this person logged in?" while authorization asks "is this specific person allowed to touch this specific object?" I've sat through code reviews where the app answers the first question fine and never gets around to asking the second one at all. That gap is where BOLA lives, and it's bigger than most engineering teams want to admit.
Why REST APIs are structurally more exposed than other application surfaces
REST is built around resources. Every object gets a URL, every URL carries an identifier, and that's the whole point of the design. Object IDs end up scattered everywhere as a result: the URL path (/api/orders/5678), query strings (?user_id=1234), request bodies, sometimes a session token. Each spot is a place where someone can swap the ID and see what comes back.
Sequential numeric IDs are the worst offenders, and I mean that literally. If I'm invoice 10042, I don't need to guess anything to reach invoice 10041, because I just subtract one. No brute-force tooling, no wordlists, just arithmetic a ten-year-old could do.
Multi-tenant SaaS turns this from a bad problem into a genuinely ugly one. Miss one ownership check and you can expose an entire tenant's database, their file storage, their configuration, to a different paying customer sitting on the same platform.
GraphQL doesn't get a pass either, whatever some teams tell themselves. Authorization has to live at the resolver level, where data actually gets pulled, and gets skipped too often at the schema level, where types just get declared. I've seen beautifully typed schemas with zero resolver-level checks, and that's a sloppy REST endpoint wearing a nicer outfit.
REST's statelessness is usually a good thing; every request stands on its own. The catch is that every request then has to carry its own authorization context and get checked on arrival, with no session quietly backing it up. Miss that check once, on one route, and the route stays open indefinitely. The categories that keep showing up in real incidents include user profiles, file download links, order and invoice records, admin role fields, health records, and financial accounts. Anything with an owner is a candidate.
What an IDOR/BOLA attack actually looks like step by step
It starts with logging in as a legitimate user, with no stolen credentials, no phishing, nothing exotic. My app sends GET /api/invoices/10042 to pull up my own invoice. I open dev tools, or intercept the request with Burp, and change the number: 10041, 10040, on down the line.
If the server checks my session token but never checks whether my session actually owns invoice 10041, it hands the data over anyway. Authentication passed, but authorization never ran.
It doesn't stop at reading, either. PUT /api/invoices/10041 can rewrite someone else's record, and DELETE /api/invoices/10041 erases it outright. If the same missing check applies across verbs, BOLA turns into a full write-and-delete surface, not just a peeping problem.
There's a privilege-escalation flavor too: change a role or plan field in a request body and upgrade your own account to premium without paying, because the server just trusted whatever the client said its tier should be. And there's the cross-tenant flavor common in SaaS, where you swap in another company's tenant ID and walk straight into their data. Same broken mechanism, much bigger blast radius.
First American's EaglePro platform is the case I bring up most, because it's so plain. Any authenticated user could edit a URL parameter and pull up escrow and title documents belonging to someone else. No exploit chain, no SQL injection, no cross-site scripting, no malware required — just a browser and the willingness to change a number in an address bar.
How prevalent this vulnerability actually is, and who has paid for it
OWASP has ranked BOLA the number one risk in the API Security Top 10 for three cycles running. Its parent category, Broken Access Control, holds the number one spot in the OWASP Web Top 10 too, including the 2025 edition. That's not a coincidence, and it's not some niche finding buried on page twelve of a PDF nobody reads.
Salt Security's research puts BOLA at close to 40% of all recorded API attacks. Nothing else in the API threat landscape comes close to that share.
Dell's 2024 incident followed the pattern almost exactly. Attackers set up fake partner accounts, then manipulated API object references to pull records tied to tens of millions of customers. Nobody bypassed a clever defense; the checks just weren't there. First American's flaw needed nothing beyond editing a parameter, which tells you the root cause was structural, not some attacker outsmarting a well-built system.
This isn't a problem limited to companies still figuring things out, either. Uber, Facebook, and Trello have all had BOLA variants surface at different points, across wildly different company sizes and engineering maturity. That consistency says something: BOLA isn't a junior-engineer mistake, but a missing design pattern that experienced teams skip about as often as inexperienced ones do.
Noname Security's 2023 survey found 95% of organizations had dealt with an API security incident in the prior twelve months, and 20% of those said BOLA was the cause. The first record an attacker grabs isn't really what drives the cost up. Sequential enumeration makes the whole dataset reachable the moment the flaw exists, and the attacker decides where to stop, not you.
Why automated scanners reliably miss BOLA while catching other vulnerability classes
BOLA is a logic error, not a syntax error, and nearly everything about why it slips past otherwise-decent tooling comes down to that one fact.
SQL injection leaves a fingerprint: a malformed payload, an odd character, a query breaking in a recognizable way. A scanner pattern-matches against that fine. BOLA leaves nothing to match. A vulnerable request comes back as a perfectly well-formed HTTP response: status 200, valid JSON, structurally identical to a legitimate one. The only thing wrong is that the data belongs to somebody else, and a scanner has no concept of who's supposed to own what.
Confirming BOLA takes two authenticated sessions running side by side, requesting the same object, and comparing whether access got denied the way it should have. That takes understanding the app's ownership model. Most DAST tools never see source code, only inputs and outputs, so they genuinely can't tell an ownership check is missing. All they can tell you is that the request worked.
A scan certificate is not proof of a penetration test. If your audit evidence is a vulnerability scan report, it did not surface BOLA. Auditors who accept that as sufficient are accepting a gap they probably don't even realize exists.
BFLA, Broken Function Level Authorization, is BOLA's cousin and has the same blind spot for the same reason: no syntactic signal, just a missing logical check that nothing automated is built to notice.
How a structured manual test covers the BOLA attack surface
Good testing starts with a map, before anyone touches a single request. List every object identifier the API exposes: path parameters, query strings, request bodies, claims tucked into session tokens. Note the identifier type while you're at it. Sequential numeric IDs are highest risk, UUIDs cut down casual enumeration but don't kill it outright, and opaque tokens are safer still, though "safer" isn't the same word as "safe."
Five scenarios cover the ground from there. Read access, using your own session, requesting another user's object by ID to see if the server hands it over. Write access, trying to modify someone else's object and checking whether the change goes through. Delete access, trying to delete it and seeing if the server complies. Cross-tenant access, for multi-tenant apps, swapping in another organization's tenant-scoped ID to find out if isolation actually holds up under pressure. Role and privilege manipulation, altering a role or plan field in the request body and checking whether entitlements get enforced server-side, or just taken on faith from the client.
The minimum setup is two accounts, User A and User B, both under the tester's control. Authenticate as A, then request B's objects. That's the whole harness.
GraphQL needs the same rigor, applied at the resolver level, since that's where data actually gets pulled rather than where the schema gets declared on paper.
A real finding here looks like a working exploit: the actual request, the actual response, the actual data that came back. With source code access, a tester can go further, tracing every object identifier through the codebase and flagging every route missing an ownership check, with no sampling and no guesswork.
The authorization design patterns that eliminate BOLA at the code level
Never trust the client to hand you a correct or authorized identifier. That's the whole rule. Ownership gets derived server-side, from the authenticated session, every single time, with no exceptions carved out for convenience.
Session-scoped data access is the first pattern worth building around. Pull the user ID from the server-side session or a validated JWT, never from the request body or URL. A query like SELECT * FROM invoices WHERE id = ? AND owner_id = ? needs that second bind parameter to come from the session, not from anything the client sent over. If the result comes back empty, return a 404, not a 403, because a 403 confirms the object exists while a 404 tells the attacker nothing at all.
Indirect references are the second pattern. Map the ID the client sees to the real internal database key, server-side, so the client never touches the actual primary key. Enumeration stops paying off once external references go opaque and get scoped to that one session.
A centralized authorization layer is the third pattern, and it's the one that holds up best over years, not just at launch. Scatter authorization checks across dozens of individual controllers and you get inconsistency almost by default: one team remembers the check, the next team forgets it under deadline pressure. A single authorization service or middleware layer that every route has to pass through gives you both consistency and an audit trail worth having.
Switching from sequential integers to UUIDs helps against casual enumeration. Don't mistake that for a fix, though, because a UUID with no ownership check behind it is still exploitable the moment it leaks anywhere: a log file, a referrer header, a support ticket screenshot someone forwards around.
There's a related surface worth naming: BOPLA, Broken Object Property Level Authorization. Even with object-level access locked down correctly, the API still has to avoid handing back fields the requesting user isn't cleared to see. Field-level filtering is its own requirement, related to BOLA but not fixed by the same code.
None of this sticks without tests, either. Every ownership check needs an automated test running on every pull request. A fix nobody's testing for is a fix waiting to get quietly reverted six months down the road when someone refactors that route.
Why this class of bug requires human expert review to catch and confirm
If your pen test vendor leans mainly on automated tooling, they will not reliably find BOLA, because that's just not what those tools are built to catch. Ask directly what manual methodology they run for authorization flaws specifically. A vague answer is itself the answer.
Whitebox access changes the whole equation. With source code in hand, a reviewer enumerates every route and every object identifier in the codebase, instead of sampling a black-box surface and hoping to land on the right endpoint. Spotting a missing ownership check comes from reading the function, not guessing which of a thousand routes might be broken. It's faster, and it's more thorough, because there's no reconnaissance tax eating into the engagement hours.
A credible finding looks like a documented exploit: the request, the response, the session context, laid out clearly enough that anyone on the engineering team can reproduce it in five minutes. A line buried in a report saying object IDs "appear in URLs and could theoretically be an issue" tells nobody anything useful.
Fixes need a retest too. Authorization logic gets touched by nearly every feature change that ships, and a fix at one endpoint says nothing about whether the neighboring endpoint inherited the same missing check. Annual point-in-time testing misses most of the drift that happens between cycles. Continuous testing tied to pull requests catches it the moment a new route ships, and that's really the only timing that matters.
This carries weight for compliance too, not just security posture. SOC 2, HIPAA, and ISO 27001 auditors want evidence that access controls were actually tested and that findings were proven, not just asserted in a bullet point. A report that lists BOLA as a finding with no working exploit attached is easy to dispute and hard to act on with any confidence. I've watched auditors push back on exactly that kind of report, and the engineering team has nothing to point to except a scanner log that never should have counted as evidence in the first place.


