Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Broken Object Level Authorization (BOLA) occurs when an API accepts an object ID from the client but fails to verify that the authenticated caller may access that specific object. In a hypothetical employee-directory API, changing employee_id=1042 to another observed or guessed value could return a different person’s profile if the server checks only that the caller is logged in. Sequential or otherwise predictable IDs make discovery easier, but they are not the underlying vulnerability: the defect is the missing per-object authorization decision.

What the employee-directory scenario actually means

Imagine an API endpoint such as GET /api/employees/1042. An employee signs in, receives their own profile, and notices the numeric identifier in the request. If changing that identifier to 1043 returns another employee’s record without an authorization error, the endpoint has a BOLA flaw. The example explains the mechanism; it does not establish that a named employer, a particular number of workers, or a real breach occurred.

Every object reference supplied by a client is only a pointer to a record. It is not proof that the caller is entitled to use that record. The API must make an authorization decision for the authenticated principal, the requested object, and the requested operation on every request.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

BOLA, IDOR and BFLA are related but different

  • BOLA: the caller can invoke the API function, but the server fails to check permission for the particular object, such as another employee’s profile.
  • Insecure Direct Object Reference (IDOR): a common way BOLA appears, where a direct identifier in a URL, query, form field, header, or request body is trusted without an adequate access check.
  • Broken Function Level Authorization (BFLA): the caller should not be allowed to invoke the function at all, such as an administrator-only operation. That is a function-level failure rather than an object-level one.

A role check alone may not solve BOLA. A user can have a valid “employee” role and still be prohibited from reading a particular colleague’s private record, a different tenant’s data, or an object outside their reporting relationship.

How predictable IDs enable enumeration

Enumeration is an access pattern, not a separate authorization control. It becomes possible when an endpoint accepts many references and does not consistently enforce object-level rules. Predictable numbers reduce the effort needed to try other references, but a flaw can exist with UUIDs, usernames, vehicle identification numbers, project names, or any other identifier if a caller can obtain or guess one and the server fails to check permission.

  1. The client authenticates and makes a legitimate request for an object it is allowed to view.
  2. The response, list view, error message, notification link, or browser traffic reveals an object reference.
  3. The client changes that reference and sends another request while keeping the same session.
  4. The server retrieves the referenced record by primary key or other identifier.
  5. If no object-level policy runs before the response, records outside the caller’s permitted scope are returned. Repeating the process creates an enumeration pattern.

A bulk endpoint can create the same result without changing IDs one at a time. For example, a request that returns every employee in a tenant may disclose records beyond the caller’s allowed department if the list query is not scoped. Nested routes, such as /employees/1042/payroll, and GraphQL fields or mutations require their own checks; protecting a top-level profile route does not automatically protect every related resource.

What a BOLA flaw can expose or change

Impact depends on the operation and the data behind it. A read endpoint might disclose profile, contact, compensation, or other sensitive fields; the exact fields cannot be inferred from the identifier alone. An update endpoint could allow unauthorized edits, while a delete operation could destroy another person’s object. Export, search, batch, and synchronization functions can disclose many records in one response. OWASP describes severe cases in which object-level failures contribute to account takeover, but that outcome requires additional conditions and should not be assumed for every directory API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not claim that employee information was exposed, modified, or deleted without incident-specific evidence. The technically accurate statement for a hypothetical route is that missing authorization could permit those outcomes if the endpoint and data model allow them.

API surface Object-level question Typical consequence if unchecked
Single-object read May this caller read this employee or document? Unauthorized disclosure of one record
List or search Is every returned object within the caller’s scope? Overly broad directory or search results
Create May this caller create an object for this tenant, project, or owner? Objects attached to an unauthorized scope
Update May this caller change this specific object? Unauthorized data or permission changes
Delete May this caller remove this object? Destruction of another user’s data
Export or bulk action Does the entire batch satisfy the caller’s scope? Large-scale disclosure or modification
Nested resource Does access to the parent imply access to this child? Unauthorized payroll, files, comments, or tokens
GraphQL query or mutation Are IDs in every field and mutation authorized? Data access or deletion through an alternate path

How to test for BOLA safely

Test only systems for which you have explicit authorization. A production service, public directory, or third-party API is not a safe practice target merely because its identifiers are visible. Use a controlled environment with test data and accounts whose permissions are intentionally different.

Build a two-account test case

  1. Create two accounts with distinct access scopes, such as separate tenants, departments, or ownership relationships.
  2. Create or identify one object that account A may access and a different object that only account B may access.
  3. Authenticate as account A and capture a normal request for A’s object from documented API traffic or an approved test client.
  4. Change only the object reference to B’s object, then resend the request. A correct implementation should deny it or return a response that does not reveal protected data.
  5. Repeat the check for each relevant method and object type. A successful read denial does not prove that update, delete, export, or mutation paths are protected.
  6. Where the application uses nested routes, list endpoints, GraphQL IDs, or batch requests, test those paths separately with the same two-account setup.

Use identifiers that the second session can legitimately observe, such as values in an approved list response, an error, or a notification link. The goal is to verify authorization behavior, not to scan an unknown service. Record the request, authenticated identity, object relationship, expected policy, status code, and whether any protected fields appeared in the response.

Interpret results carefully

  • A denial for one route and method demonstrates an effective check for that request path only.
  • Different status codes are not enough to establish safety if a supposedly denied response still contains sensitive fields, counts, or metadata.
  • A successful request is not automatically BOLA if the test account is legitimately authorized; verify the intended relationship and policy first.
  • Inconsistent behavior across equivalent REST routes, GraphQL operations, bulk endpoints, or service versions is a strong reason to review the authorization layer and add regression tests.

How to prevent IDOR and BOLA in an API

Enforce authorization on the server

The decisive check belongs in the API, gateway, or another trusted server-side layer. Client-side hiding, disabled buttons, or a mobile application’s local role check can improve usability but cannot protect an object. For each request, derive the authenticated principal from the validated session or token, resolve the requested object, and evaluate the policy before returning or changing data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Scope the lookup, rather than fetching first

A safer data-access pattern limits the query to objects the caller may use. Conceptually, instead of retrieving any employee by primary key and hoping a later layer rejects it, query for the requested ID within the caller’s permitted employees, tenant, projects, or ownership set:

permitted = policy.objects_readable_by(current_principal)
record = employees.find(id=requested_id, within=permitted)
if record is absent:
    deny_without_disclosing_whether_it_exists()
return record

The exact implementation varies by framework and database, but the important property is that the authorization scope participates in every read, update, and delete decision. Avoid relying solely on a broad role such as “employee” when the rule also depends on tenant, ownership, managerial relationship, or object state.

Rank #4
API Security in Action
  • API Security in Action
  • Manning Publications
  • ABIS BOOK

Cover every object access path

Inventory more than profile GET requests. Apply policy checks to creates, edits, deletes, exports, downloads, searches, batch jobs, nested resources, asynchronous workers, webhooks, and GraphQL queries or mutations. A background task should receive a constrained identity or an explicit authorization context rather than bypassing the same rules used by the interactive API.

Treat bulk responses as collections of decisions

For list, search, and export operations, filter the result set by the caller’s permitted scope before pagination, aggregation, or serialization. Do not fetch all rows and remove unauthorized items only in a presentation layer. Check batch inputs individually or reject a batch when any requested object falls outside policy, according to the application’s documented behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use difficult-to-guess IDs only as defense in depth

Random or non-sequential identifiers can make casual guessing and automated discovery harder. They do not grant permission. If a complex identifier is obtained from a log, referral, notification, browser history, or another leak, the API must still deny a caller who lacks access. Identifier design should therefore be paired with server-side authorization, not substituted for it.

Log and monitor access patterns

Record enough context to investigate unusual access while respecting the sensitivity of the data: authenticated principal, object or collection type, operation, decision, tenant or scope, and request correlation information. Monitoring repeated changes to otherwise similar identifiers, high-volume object reads, unexpected cross-tenant denials, and unusual bulk requests can help detect probing. Logs are a detection and investigation aid; they do not replace a preventive authorization check.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A practical review checklist

  • Can every client-supplied object reference be traced to a server-side policy decision?
  • Does the policy evaluate the specific object and operation, not only a general role?
  • Are identifiers accepted in paths, query parameters, headers, request bodies, and GraphQL variables all covered?
  • Are list, search, export, batch, nested, and asynchronous paths scoped to the caller?
  • Do update and delete checks exist independently of read checks?
  • Do two test accounts with different permissions produce the expected allow and deny results?
  • Does a denial avoid leaking protected fields, existence signals, or excessive metadata?
  • Are non-sequential identifiers treated as friction rather than authorization?
  • Are authorization decisions and suspicious enumeration patterns logged appropriately?
  • Do automated regression tests run whenever routes, schemas, policies, or data-access code changes?

What a defensible conclusion looks like

For the generic employee-directory scenario, the central finding is precise: a client-visible or predictable ID is not itself a security failure. The failure occurs when the API accepts that ID and returns, changes, or deletes an object without verifying the caller’s permission for that object and operation. Enumeration is the way an attacker or tester may exploit the gap. Fixing the gap requires consistently scoped, server-side authorization across individual, nested, bulk, and alternate API paths, with identifier randomization and monitoring used only as supporting controls.

Quick Recap

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.