BOLA Vulnerabilities Explained in API Security

A single predictable ID can expose someone else’s data if the API trusts the user too much. That is the core risk behind Broken Object Level Authorisation, usually shortened to BOLA.
BOLA is one of the most common and damaging API security flaws because it hides in normal API behaviour. The request looks valid. The endpoint exists. The user is authenticated. The problem is that the API fails to check whether that user is allowed to access the specific object being requested.
For example, a mobile app may call: GET /api/v1/orders/45821
If changing `45821` to `45822` returns another customer’s order, the API has a BOLA issue.
This post explains how BOLA works, why it is so common, how to test for it safely, and how engineering teams can prevent it in production APIs.

What BOLA means in API security
BOLA happens when an API exposes an object through an identifier and does not properly verify ownership or permission for that object.
An object can be almost anything the application manages:
A user profile
An invoice
A bank account
A medical record
A support ticket
A delivery address
A file attachment
A chat message
A project, team, or workspace
The “object level” part matters. Authentication only proves who the user is. Authorisation decides what that user can access.
A user being logged in is not enough. The API must ask a second question on every sensitive object request:
Is this authenticated user allowed to perform this action on this specific object?
If the answer is not checked clearly and consistently, the API may leak or modify data belonging to other users.
BOLA Vulnerabilities are also known as IDOR, which stands for Insecure Direct Object Reference. IDOR is the older and more general term. BOLA is used widely in API security because modern APIs often expose object IDs through REST paths, query strings, GraphQL arguments, mobile traffic, and JSON request bodies.
Why BOLA is so common
BOLA is common because APIs are designed to expose structured data. IDs are part of normal API design.
A frontend often needs to request a specific resource:
GET /api/v1/users/7312
GET /api/v1/accounts/acc_9041/statements
GET /api/v1/projects/120/filesThat is not unsafe by itself. The risk appears when the backend treats the supplied ID as proof of permission.
A common weak pattern looks like this:
user = authenticate(token)
order = find_order(request.path.order_id)
return orderThe safer pattern adds an authorisation decision:
user = authenticate(token)
order = find_order(request.path.order_id)
if not can_access(user, order):
return 403
return orderThe missing check may seem small, but the impact can be severe.
BOLA also appears because teams split responsibilities. Frontend developers may hide buttons for unauthorised actions. Backend developers may assume the frontend will not send forbidden IDs. Attackers do not use the frontend as intended. They send direct API requests.
Other common causes include:
Sequential numeric IDs such as `1001`, `1002`, `1003`
Shared endpoints used by many user roles
Reused controller logic without permission checks
Complex tenant, organisation, or workspace models
Over-trust in JWT claims
Missing tests for negative access cases
GraphQL resolvers that fetch objects directly
Admin and customer APIs sharing service methods
The issue is not the use of IDs. The issue is using IDs without a reliable permission check.
How a BOLA flaw usually looks
A BOLA test often starts with two accounts. Account A owns one object. Account B owns another. The tester captures a request from Account A and replaces the object ID with the ID from Account B.
Here is a simple example.
Account A requests its own profile document:
GET /api/v1/documents/doc_81K2
Authorization: Bearer token_for_account_aThe API returns:
{
"id": "doc_81K2",
"ownerId": "user_a",
"fileName": "aadhaar-upload.pdf"
}Now the tester changes the ID:
GET /api/v1/documents/doc_92P7
Authorization: Bearer token_for_account_aIf the API returns Account B’s document, it is vulnerable.
The same issue can affect write operations:
PATCH /api/v1/addresses/address_5009
Authorization: Bearer token_for_account_a
Content-Type: application/json
{
"city": "Pune"
}If Account A can update Account B’s address by changing the ID, the bug is more than a data leak. It becomes unauthorised modification.
BOLA can also show up in less obvious places.
Query parameters
GET /api/v1/reports?userId=7312If a normal user can change `userId`, the API must verify that the requested user is within their permission scope.
Request bodies
{
"accountId": "acc_2044",
"amount": 5000
}The backend should not blindly trust `accountId` from the client.
Nested resources
GET /api/v1/companies/88/users/991The API must check access to the company and the user, not just whether both exist.
GraphQL arguments
query {
invoice(id: "inv_7721") {
id
total
customerEmail
}
}Every resolver that returns object data needs an authorisation check.
BOLA impact depends on the object
Not every BOLA issue has the same severity. The risk depends on the object type, data sensitivity, and allowed action.
A read-only issue may expose names, phone numbers, order history, tax details, invoices, or documents. In India, this could involve personal data such as addresses, identity documents, payment references, or employment records. That can become a privacy, compliance, and trust problem.
A write-level issue can be worse. Attackers may change delivery addresses, cancel bookings, alter profile details, add users to teams, or connect external accounts.
A delete-level issue can disrupt business operations: DELETE /api/v1/projects/120
If the API only checks whether the user is logged in, a low-privilege user could delete objects outside their scope.
The most serious cases involve money movement, account takeover paths, medical data, legal records, or internal admin objects.
A helpful way to assess impact is to ask:
What data does the object contain?
Can the object be changed or deleted?
Can access to this object lead to another sensitive action?
Does the object belong to another user, organisation, tenant, or role?
Would the affected user expect this data to remain private?
A practical prompt for reviewing an API for BOLA
Use the following technical prompt when reviewing API endpoints, writing test cases, or guiding an internal security assessment.
Review the following API endpoint for Broken Object Level Authorisation.
Endpoint:
[HTTP method and path]
Request example:
[Headers, path parameters, query parameters, and JSON body]
Authenticated user context:
[User ID, role, tenant, organisation, subscription, or ownership details]
Object being accessed:
[Object type, object ID, owner ID, tenant ID, sensitivity level]
Expected permission rule:
[Who should be able to read, create, update, delete, or list this object]
Analyse the endpoint for BOLA risk.
Check whether:
The endpoint accepts object identifiers from the client.
The backend verifies that the authenticated user can access the exact object.
The check covers tenant, organisation, role, ownership, and relationship rules.
The check happens before returning, changing, or deleting data.
The same rule is enforced for REST, GraphQL, mobile, and internal API paths.
The endpoint prevents horizontal access between users at the same role.
The endpoint prevents vertical access from low-privilege users to admin objects.
The endpoint handles nested resources securely.
Error responses do not leak whether forbidden objects exist.
10. Automated tests cover allowed and denied access cases.
Return:
A clear risk rating
The likely vulnerable parameters
The required authorisation checks
Safe test cases using two controlled test accounts
Recommended code-level fixes
This prompt is useful because it forces the review to look beyond authentication. It focuses on the exact object, the exact user, and the exact permission rule.
How to test for BOLA safely
BOLA testing should use accounts and objects that you own or have explicit permission to use. This matters in internal security reviews, staging environments, and any Bug Bounty work.
A safe test setup usually includes:
Create two test users
Use Account A and Account B under controlled conditions.
Create separate objects
For example, each account creates its own order, file, ticket, or profile record.
Capture valid requests
Use browser developer tools, a proxy, API logs, or approved testing tools.
Swap object identifiers
Replace Account A’s object ID with Account B’s object ID.
Observe the response
A secure API should return `403 Forbidden`, `404 Not Found`, or another controlled denial response.
Repeat for actions
Test read, update, delete, list, export, and share operations if they are in scope.
The expected result is simple: Account A should not be able to access Account B’s object unless the business rule clearly allows it.
Be careful with production systems. Do not access real user data. Do not change or delete records. Use test accounts, low-risk objects, and a clearly approved scope.

How to prevent BOLA in API design
The best BOLA defence is strict server-side authorisation. Frontend checks help user experience, but they do not protect the API.
Check permissions on every object request
Every endpoint that reads, updates, deletes, exports, shares, or lists objects should enforce object-level access.
A good rule looks like this:
function getInvoice(user, invoiceId):
invoice = invoiceRepository.findById(invoiceId)
if invoice is null:
return 404
if not policy.canReadInvoice(user, invoice):
return 403
return invoiceThe policy should live in a consistent layer, not scattered across controllers.
Avoid trusting client-supplied ownership fields
Do not let the client decide ownership:
{
"ownerId": "user_123"
}Use the authenticated user context from the server instead:
ownerId = authenticatedUser.idFor organisation or tenant systems, resolve membership on the server. Do not accept `tenantId` as proof that the user belongs to that tenant.
Use policy-based access control
Centralised policies reduce mistakes. For example:
canUpdateProject(user, project):
return user.tenantId == project.tenantId
and user.role in ["admin", "manager"]This is easier to test than custom logic inside every controller.
Test denied access cases
Many teams only test the happy path. BOLA prevention needs negative tests.
For each sensitive endpoint, add tests such as:
User can access their own object
User cannot access another user’s object
User cannot access another tenant’s object
Low-privilege user cannot access admin object
Removed team member cannot access old team resources
Disabled user cannot continue using cached tokens
These tests should run in CI along with normal API tests.
Common mistakes that leave APIs exposed
Teams often add partial fixes that look safe but still leave gaps.
Mistake | Why it fails | Better approach |
Hiding IDs in the frontend | Attackers can inspect traffic | Enforce backend permission checks |
Using UUIDs only | Harder to guess does not mean authorised | Check access for every object |
Checking role but not ownership | Same-role users may access each other’s data | Check role and object relationship |
Checking parent but not child object | Nested resources may cross boundaries | Validate the full resource chain |
Returning full objects before filtering | Sensitive data may already be exposed | Filter in the query or service layer |
Relying on API gateway authentication | Gateways rarely know object ownership | Add object checks in the application |
UUIDs, gateways, and frontend controls can help, but they are not substitutes for authorisation logic.
What secure behaviour looks like
A secure API treats every object ID as untrusted input. It does not matter whether the ID came from a URL, query string, body, cookie, mobile app, or GraphQL request.
Secure behaviour includes:
The API authenticates the user.
The API loads the requested object carefully.
The API checks the user’s permission for that exact object.
The API denies access before returning sensitive data.
The API logs suspicious denied access patterns.
The API uses consistent error handling.
The API tests both allowed and denied paths.
For sensitive applications, logs should record denied object access attempts without storing unnecessary personal data. Patterns such as repeated access to sequential IDs may signal probing.
The key takeaway
BOLA is dangerous because it looks like ordinary API traffic. The request is valid, the token is valid, and the endpoint works. The failure sits in the missing question: should this user access this object?
The most reliable fix is not hiding IDs or using longer random strings. Build object-level authorisation into the backend, apply it consistently, and test it with controlled accounts.
If an API accepts an object ID, assume it needs an authorisation check. That single habit prevents a large class of serious API security failures.




Comments