If I had to pick the single most common and most damaging API vulnerability I come across, it would be Broken Object Level Authorization, or BOLA. It sits at the top of the OWASP API Security Top 10 for good reason — it’s simple to understand, simple to test for, and shockingly common even in mature, well-funded applications. In this guide, I’ll explain exactly what BOLA is, why it happens so often, and the full methodology I use to find it.
What Is Broken Object Level Authorization?
BOLA happens when an API endpoint checks that a user is authenticated, but fails to check whether that specific user is actually authorized to access the specific object being requested. In plain terms: the API confirms “you’re logged in,” but never confirms “you’re allowed to see this particular record.”
The classic example looks like this:
GET /api/orders/1001
I’m logged in as a regular user, and this returns my order. Now I simply change the ID:
GET /api/orders/1002
If this returns someone else’s order — an order I never placed and have no relationship to — that’s BOLA. The API authenticated me just fine; it just never checked that order 1002 actually belongs to me.
You might also hear this called IDOR (Insecure Direct Object Reference) — BOLA is essentially the modern, API-focused evolution of that same underlying concept.
Why BOLA Happens So Often
From what I’ve seen testing dozens of APIs, BOLA keeps showing up because:
- Developers correctly implement authentication (checking “is this a valid logged-in user?”) but forget authorization (checking “does this user own this specific resource?”) are two completely separate concerns.
- Authorization checks are added to some endpoints but forgotten on others, especially newer or less-visible ones like export, admin, or reporting endpoints.
- Object IDs are often sequential integers or easily guessable UUIDs, making enumeration trivial once the authorization gap exists.
- Nested resources (
/api/users/5/orders/1002) sometimes only validate the outer object, not the inner one. - Different HTTP methods on the same resource path aren’t tested equally — a
GETmight be properly protected while aDELETEorPUTon the same object isn’t.
Step 1: Mapping Every Endpoint That References an Object
Before testing, I go through the entire API and list every endpoint that takes an identifier as a parameter — whether in the URL path, query string, or request body:
GET /api/users/{id}
GET /api/orders/{orderId}
PUT /api/profile/{userId}
DELETE /api/documents/{docId}
POST /api/messages/{conversationId}/reply
GET /api/invoices?accountId={accountId}
Any place an ID appears is a candidate for BOLA testing. I don’t skip anything, including endpoints that seem “low value” — attackers don’t skip them either.
Step 2: Creating Two Test Accounts
I always set up at least two accounts (User A and User B) with no special relationship to each other. This is non-negotiable for proper BOLA testing — testing with a single account or with admin credentials will hide these bugs completely.
Step 3: The Core BOLA Test
For every identified endpoint, I:
- Log in as User A and create or access a resource, noting its ID.
- Log in as User B (different session/token).
- Attempt to access User A’s resource ID using User B’s session.
- Check whether the response returns User A’s actual data, or is properly denied.
I test this across every HTTP method the endpoint supports:
GET /api/orders/1001 (as User B) — can I view it?
PUT /api/orders/1001 (as User B) — can I modify it?
DELETE /api/orders/1001 (as User B) — can I delete it?
It’s common to find that GET is protected but PUT or DELETE isn’t, because a developer remembered to secure the “view” logic but forgot the “edit” or “remove” logic.
Step 4: Testing Indirect Object References
Not every BOLA vulnerability is as obvious as an ID in the URL. I also test:
- IDs hidden in the request body rather than the URL, since some developers only add authorization checks to path parameters and forget body parameters.
- IDs in query strings, like
?accountId=456, which are easy to overlook during code review. - IDs inside JWTs or tokens that get echoed back and reused — if an endpoint trusts an ID from a client-supplied token field instead of re-verifying ownership server-side, that’s a red flag.
- Batch/bulk endpoints —
POST /api/orders/bulk-exportwith a list of IDs in the body; I test supplying a mix of my own IDs and someone else’s to see if the bulk operation checks ownership per item or just checks that I’m authenticated at all.
Step 5: Testing Nested and Relational Resources
APIs with nested resource structures need extra scrutiny:
GET /api/organizations/{orgId}/projects/{projectId}/tasks/{taskId}
I test:
- Does the API verify that
projectIdactually belongs toorgId? - Does it verify that
taskIdactually belongs toprojectId? - Or does it only check that I have access to some organization, and then blindly trust the rest of the path?
I specifically test mismatched combinations — my own orgId paired with someone else’s projectId and taskId — to see which level of the hierarchy actually gets validated.
Step 6: Testing ID Enumeration and Predictability
Even where authorization checks exist, I check how guessable the IDs themselves are:
- Sequential integers (
1001,1002,1003) make enumeration trivial once any BOLA gap is found. - UUIDs are much harder to guess, but I still check whether they’re ever leaked elsewhere in the application (in URLs, emails, other API responses, or error messages) where an attacker could harvest them.
- Predictable patterns — some systems use IDs based on timestamps or user IDs combined in predictable ways, which I test for patterns using multiple accounts created close together in time.
Step 7: Testing GraphQL-Specific BOLA
Since GraphQL uses a single endpoint, BOLA testing looks a little different — I test whether individual resolvers validate ownership on every object field, not just the top-level query:
query {
order(id: "1002") {
id
total
customer {
email
address
}
}
}
I specifically test nested object resolvers, since a common mistake is validating access on the top-level order field but not re-validating on the nested customer object it returns.
Step 8: Automating BOLA Discovery at Scale
For APIs with hundreds of endpoints, manual testing every combination isn’t practical, so I also:
- Use Burp Suite’s Autorize extension, which automatically replays every request captured while browsing as User A, using User B’s session token, and flags any that return identical data.
- Write custom scripts that iterate through a range of IDs using a low-privilege token and log which ones return valid (non-403/404) responses.
- Diff response bodies between the “should be denied” request and a known “properly denied” request to catch cases where the status code is correct (like
200 OK) but the actual data returned is subtly different or partially leaked.
Common Places I Keep Finding BOLA
- Export and reporting endpoints, which are often built later and get less security review than core CRUD endpoints.
- Admin panels that reuse the same API as the regular app but assume “no one will call this without the admin frontend.”
- File and document download endpoints using a direct file ID or path.
- Messaging/chat features where conversation IDs aren’t tied back to participant verification.
- “Duplicate” or “clone” features that let you reference another object’s ID as a template, which sometimes skips ownership checks since it’s technically a “create” operation.
How I Recommend Fixing BOLA
- Enforce object-level authorization checks on every single endpoint that accepts an object identifier, for every HTTP method, with no exceptions for “low value” endpoints.
- Centralize authorization logic into a shared, well-tested function or middleware rather than reimplementing ownership checks in each controller.
- Always re-verify ownership at the database query level — filter by
WHERE user_id = current_user_id AND id = requested_idrather than fetching by ID alone and checking ownership afterward in application code (which is easy to accidentally skip). - Use non-sequential, unguessable identifiers (UUIDs) as a defense-in-depth measure, though this should never be the only protection.
- Apply the same rigor to nested resources — validate every level of a relational path, not just the first one.
- Include automated BOLA regression tests in your CI/CD pipeline using two test accounts, so future code changes can’t silently reintroduce the gap.
Final Thoughts
BOLA is proof that the most damaging vulnerabilities aren’t always the most technically complex ones. It’s usually just a missing WHERE clause or a forgotten if check — but the impact can mean any authenticated user reading, modifying, or deleting any other user’s data across the entire platform. Every time I test a new API, this is the first thing I check, and it’s rarely a wasted effort.