1. What Is It?
Broken Object Level Authorization (BOLA) occurs when an API does not properly verify whether a user has permission to access a specific resource.
In practice, a user can access resources they do not own by modifying identifiers such as IDs, paths, or parameters.
IDOR and BOLA are often used in similar contexts. Later in another guide, we will explain where they overlap and where they diverge.
This issue is fundamentally an authorization problem. We covered authentication in the previous guide, including how identity is established before access decisions are made.
Read the Authentication Guide2. Why It Matters
BOLA is dangerous because:
- It directly exposes sensitive data.
- It does not require bypassing authentication.
- It is easy to exploit, often by only changing an ID.
- It scales, attackers can enumerate large datasets.
In real-world systems, this can lead to:
- User data leaks.
- Financial data exposure.
- Account takeover scenarios.
- Cross-tenant breaches in SaaS platforms.
Why OWASP classifies it as API1
APIs frequently expose object identifiers in paths, query strings, headers, or payloads. That makes object access a very large and very common attack surface. OWASP places BOLA first because any endpoint that receives an object ID and performs an action on that object must enforce object-level authorization, and failures here can lead to unauthorized disclosure, modification, or destruction of data.
3. How It Happens (Technical)
BOLA happens when APIs rely on client-controlled identifiers without verifying ownership.
Typical flow: GET /api/users/123 with an authenticated token.
The backend usually:
- Verifies the user is authenticated.
- Fetches the object using the provided ID.
- Returns the resource.
Missing step: checking whether the resource actually belongs to the user.
Core Issue
handlers/resource.py# vulnerable
resource = get_resource(request.params.id)
return resourceCorrect Implementation
handlers/resource.py# secure
resource = get_resource(request.params.id)
if resource.owner_id != current_user.id:
raise ForbiddenError()
return resourceKey concept: Object-level authorization means: Can this specific user access this specific resource? Without this check, BOLA exists.
Attacker’s Perspective
From an attacker’s point of view, BOLA is not about breaking authentication. It is about testing whether object boundaries are actually enforced.
- Identify endpoints that expose object identifiers.
- Change IDs incrementally or try known valid values.
- Replay the same request with different accounts.
- Compare responses for unauthorized data differences.
If a request returns another user’s data after only changing an identifier, the system is vulnerable.
4. Real-World Example
BOLA can occur in any API that exposes object identifiers. Multi-tenant systems are a common case where the impact becomes more severe due to broken isolation boundaries.
Example endpoint:
GET /api/tenants/{tenant_id}/invoices/{invoice_id}Expected behavior: users should only access invoices within their own tenant.
Vulnerable Logic
api/invoices.pydef get_invoice(tenant_id, invoice_id, claims):
invoice = INVOICES.get(invoice_id)
if invoice["tenant_id"] != tenant_id:
raise NotFound()
return invoiceWhat is wrong: The API checks invoice-to-tenant mapping from the URL but does not verify whether the authenticated user belongs to that tenant.
Attack example:
GET /api/tenants/tenant-beta/invoices/inv-b-900A user from tenant-alpha sends this request.
Result: if data is returned, tenant boundary is broken and unauthorized data is exposed.
Common Variations
- Sequential ID BOLA: attackers increment numeric IDs to access neighboring records.
- UUID BOLA: even when resources use UUIDs, missing authorization still exposes data if an attacker obtains a valid identifier.
- Nested Resource BOLA: endpoints like
/users/{id}/orders/{order_id}may validate the order exists but fail to verify that it belongs to the specified user. - Indirect BOLA: access is granted through secondary identifiers such as email, slug, reference code, or filename rather than a numeric ID.
5. How To Prevent
1. Enforce object ownership on every request
Every time a resource is accessed, ownership must be verified explicitly.
authz/object_policy.pyresource = get_resource(resource_id)
if resource.owner_id != current_user.id:
raise ForbiddenError()- This check must happen after fetching the resource, not before.
- Never return a resource without validating who owns it.
2. Never trust client-supplied identifiers
IDs coming from the client are fully controlled by the user:
- URL paths
- Query parameters
- Request bodies
request-example.httpGET /api/users/123Always combine lookup (resource_id) and authorization (who owns it).
Never rely on IDs alone to grant access.
3. Validate against authenticated context
Authorization must always be derived from server-side identity, not request input.
Basic check:
authz/basic_check.pyresource.owner_id == current_user.idIn multi-tenant systems, this alone is not enough. You must also validate tenant boundaries:
authz/tenant_check.pyresource.tenant_id == current_user.tenant_idBoth conditions must pass before returning the resource.
4. Use proper access control models
Use a clear and consistent authorization model across your API.
- RBAC (Role-Based Access Control): define permissions by role (
admin,user,viewer). - ABAC (Attribute-Based Access Control): evaluate attributes such as ownership, tenant, department, and request context.
In practice, RBAC defines what actions are allowed while ABAC enforces which specific resources are accessible.
5. Centralize authorization logic
Avoid writing authorization checks directly inside every endpoint.
Instead:
- Use middleware or decorators.
- Implement policy layers.
- Create reusable authorization functions.
Example:
authz/policies.pydef can_access_invoice(user, invoice):
return (
invoice.owner_id == user.id and
invoice.tenant_id == user.tenant_id
)This approach prevents inconsistent logic, makes rules easier to audit, and reduces security bugs as the system grows.
Key Principle
- Explicit
- Consistent
- Centralized
- Based on server-side identity
6. Detection Tips (Scanner Perspective)
Detecting BOLA requires active testing of access boundaries.
Common techniques:
- ID manipulation:
/users/1to/users/2 - User context switching: test with different accounts
- Response comparison: same endpoint, different identifiers
- Pattern targeting:
/users/{id},/orders/{id},/invoices/{id}
Key signal: if the same request structure with a different identifier returns another user's data, BOLA is present.
7. Final Takeaway
BOLA is simple but critical.
It exists whenever:
- APIs trust user input.
- Ownership is not verified.
- Authorization is assumed instead of enforced.
Every request must answer: Can this user access this specific resource?
If that check is missing, the API is already vulnerable.