Application Security

IDOR and BOLA Explained: How Applications Accidentally Expose Other Users' Data

Understand IDOR and BOLA, why object IDs are not the real problem, how authorization failures expose other users' data, and how to prevent them.

Application SecurityAPI SecurityAccess ControlIDORBOLA
IDOR and BOLA Explained: How Applications Accidentally Expose Other Users' Data article cover

A user signs in to an application, opens an invoice, and sees a URL such as /api/invoices/8421. They change 8421 to 8422. If the server returns another customer’s invoice, the important failure is not that the number was easy to change. The failure is that the server accepted a request for an object without proving that the current user was allowed to access that object.

This family of access-control failures is commonly described as Insecure Direct Object Reference (IDOR) in web applications and Broken Object Level Authorization (BOLA) in API security. The terms overlap heavily. MITRE lists IDOR and BOLA as alternate terms around CWE-639, Authorization Bypass Through User-Controlled Key, while OWASP’s current IDOR guidance places the weakness under Broken Access Control.

The most useful lesson is simple: an object identifier tells the application what resource the client wants. It does not prove that the client is authorized to receive it.

IDOR and BOLA in plain language

Applications constantly work with objects: profiles, invoices, messages, files, orders, support tickets, medical records, projects and API resources. A client needs some way to tell the server which object it wants. That reference might be a sequential integer, UUID, filename, account number or another token.

OWASP defines IDOR as an access-control vulnerability that occurs when an application exposes a reference to an object and fails to verify that the requester is authorized for that object. In APIs, OWASP uses BOLA for the same core authorization problem: object identifiers arrive in requests, but object-level permission checks are missing or incomplete.

Consider two authenticated users:

  • Alice owns invoice 8421.
  • Bob owns invoice 8422.

Alice is legitimately authenticated. If she requests Bob’s invoice and the server only checks that someone is logged in, authentication succeeds but authorization fails.

GET /api/invoices/8422
Authorization: Bearer <Alice's valid session>

A secure application must answer a second question after identifying Alice: Is Alice allowed to read invoice 8422?

This distinction is why IDOR can survive in an application with perfectly functional login, MFA and session management. Authentication establishes identity. Authorization decides what that identity may do.

Object-level authorization flow showing that authentication must be followed by an ownership or policy check before an object is returned

Where the object reference can appear

The vulnerable reference does not have to be a number in a URL. OWASP’s IDOR guidance explicitly notes that object references can include filenames and other values, and modern APIs place identifiers throughout requests.

Request location Example Authorization question
URL path /orders/5912 May this user access order 5912?
Query string ?document=annual.pdf May this user retrieve that document?
JSON body {"projectId":"p_73"} May this user modify project p_73?
GraphQL argument invoice(id:"8422") May this principal read that invoice?
File reference /download/customer-b/report.pdf Is the file inside this user’s allowed scope?

The shape of the identifier changes, but the security decision does not.

Why changing an ID sometimes exposes another user’s data

A common vulnerable implementation separates database lookup from authorization incorrectly. Conceptually, the code behaves like this:

const invoice = await db.invoice.findById(req.params.id);
return res.json(invoice);

The server trusts the requested identifier and returns whatever record it resolves to. Login middleware may already have confirmed that the requester has a valid session, but nothing connects the requested invoice to the authenticated principal.

A safer pattern scopes the lookup to resources the current user is permitted to access:

const invoice = await db.invoice.findOne({
  id: req.params.id,
  ownerId: req.user.id
});

if (!invoice) {
  return res.status(404).end();
}

return res.json(invoice);

Real systems may use tenant membership, roles, relationship-based policies, attribute-based rules or a dedicated authorization service instead of a simple ownerId. The principle remains the same: the server derives authorization from trusted identity and policy data, not from a client claim that an object belongs to the user.

OWASP’s IDOR Prevention Cheat Sheet recommends looking up objects through the user’s permitted dataset rather than searching the entire collection and assuming the supplied ID is safe.

IDOR versus BOLA: is there a real difference?

For practical defensive work, they describe substantially the same object-level authorization failure. The vocabulary differs because of context.

IDOR is the older and widely recognized web-security term. BOLA is the terminology used by the OWASP API Security project, where object identifiers are especially common in API paths, parameters and bodies.

MITRE’s CWE-639 documentation explicitly lists both IDOR and BOLA as alternate terms. OWASP’s Web Security Testing Guide describes BOLA as an API failing to enforce authorization checks for each object accessed by a client.

The word direct in IDOR can sometimes distract developers into thinking the cure is to make the reference indirect or obscure. That is not the fundamental fix. If a user learns a valid UUID from a shared link, log entry, browser history or another response, a vulnerable server will still return the object.

Why UUIDs help less than people sometimes assume

Changing 8422 to a long random UUID can make enumeration much harder. That is useful defense in depth. OWASP notes that complex identifiers can make valid values difficult to guess.

But OWASP also makes the critical point that access-control checks are still essential. Identifier complexity reduces discoverability; it does not create authorization.

Horizontal and vertical authorization failures

Object-level failures often produce horizontal privilege escalation: one ordinary user reaches another ordinary user’s resource. Examples include one customer viewing another customer’s order, one student downloading another student’s private submission, or one tenant accessing a record belonging to a different organization.

The consequences can become vertical when an object reference leads to privileged resources or actions. Permission to read an object does not automatically imply permission to update, delete, approve or share it.

Multi-tenant applications need an explicit tenant boundary

BOLA becomes especially serious in software-as-a-service systems where many organizations share the same application and database infrastructure. A tenant-aware design needs authorization constraints that bind the object to the authenticated tenant and principal. The tenant boundary cannot be supplied or overridden solely by untrusted client input.

How to design object-level authorization correctly

A reliable design makes authorization an unavoidable part of retrieving or changing protected objects.

  1. Start from a trusted principal established by a validated session or token.
  2. Define a precise policy: can principal P perform action A on object O in tenant T?
  3. Enforce the decision on the server, not merely in the UI.
  4. Scope data access to resources the principal may access where practical.
  5. Apply authorization independently to read, update, delete and other operations.
  6. Use unpredictable identifiers only as defense in depth.
  7. Centralize policy where useful, while preserving object and tenant context.

A practical authorization matrix

Principal Object Read Update Delete
Project owner Own project Allow Allow Allow
Project member Member project Allow Policy dependent Deny
Ordinary user Unrelated project Deny Deny Deny
User in tenant A Tenant B project Deny Deny Deny

How defenders can test for IDOR and BOLA safely

Testing should only be performed on systems you own or are explicitly authorized to assess. A safe test environment can use two controlled accounts, A and B: create an object as A, sign in as B, repeat the request using A’s object identifier, and verify that read, update and delete operations are denied as expected.

Do not perform broad identifier enumeration against third-party systems. A controlled two-account test usually demonstrates the security property without collecting anyone else’s data.

Common fixes that do not actually fix the authorization problem

Hiding IDs in the frontend, Base64-encoding them, switching only to UUIDs, checking only a generic user role, or trusting a client-supplied userId or tenantId do not replace server-side object authorization.

Logging and monitoring still matter

Correct authorization is the primary control, but useful logs help identify probing and implementation mistakes. Useful fields can include the authenticated principal, tenant, action, object type, decision and request correlation ID without unnecessarily recording sensitive contents.

How this relates to Broken Access Control

OWASP currently classifies IDOR under A01:2025 Broken Access Control. For the wider category and server-side authorization principles, see Broken Access Control Explained, OWASP Top 10:2025 Explained, and RAG Security Explained.

A compact review checklist

  • Is the requester authenticated through trusted server-side context?
  • Is authorization evaluated for this specific object and action?
  • Is tenant membership enforced independently of client-supplied tenant IDs?
  • Do read, update, delete and other sensitive operations each enforce policy?
  • Can data access be scoped to the requester’s permitted objects?
  • Are UUIDs being used only as defense in depth rather than as authorization?
  • Have cross-user and cross-tenant tests been included?
  • Are denied object-access attempts logged appropriately?

The important mental model

An ID in a request is a routing hint. It answers which object? It does not answer whose object?, who may use it?, or what may they do with it? Every protected object operation should be evaluated against the authenticated principal, requested action and actual authorization policy before data crosses the trust boundary.

Sources and further reading