EresusSecurity
Back to Research
Application Security

BOLA and IDOR in APIs: How to Test Object-Level Authorization

Yiğit İbrahim SağlamOffensive Security Specialist
February 25, 2025
Updated: October 18, 2026
9 min read

Short answer: Broken Object Level Authorization (BOLA), often called IDOR, occurs when an API lets a caller perform an action on an object they are not permitted to access. Authentication proves who is making a request; it does not prove that the caller may read, change, or delete the requested object. Test this boundary only on systems and data you are authorized to assess.

OWASP API Security Top 10:2023 calls this risk API1:2023. Object identifiers can appear in URL paths, query parameters, headers, request bodies, GraphQL variables, file references, or job IDs. Their format—sequential, random, or opaque—does not replace a server-side authorization decision.

Separate object authorization from function authorization

BOLA concerns access to a particular object through a function the caller may otherwise use. For example, a signed-in customer may be allowed to read invoices, but not another customer's invoice. Broken Function Level Authorization (BFLA) concerns a caller reaching a function or operation they should not be able to invoke at all. An assessment may find both, but the tests and remediation questions differ.

A successful HTTP status is not proof of a vulnerability by itself. A response body, state change, or downstream effect must be compared with the expected policy and a safe control. Likewise, a 403 or 404 does not prove authorization is correct if the request already caused an unauthorized side effect.

Build a safe authorization matrix

Before testing, obtain written scope and identify a non-production environment or approved test fixtures. Create dedicated, least-privilege accounts and synthetic records; do not enumerate real customer identifiers or retrieve another user's data. Record the expected decision before sending each request.

Caller and object Read Update Delete or other state change
Owner in the same tenant Expected policy Expected policy Expected policy
Different user in the same tenant Explicitly define Explicitly define Explicitly define
User in another tenant Explicitly deny or define an approved sharing rule Explicitly deny or define an approved sharing rule Explicitly deny or define an approved sharing rule
Read-only role Usually allow only approved reads Deny unless policy says otherwise Deny unless policy says otherwise
Expired or unauthenticated session Deny protected access Deny Deny

“Explicitly define” is deliberate: systems differ. A support role, shared workspace, or delegated account may have legitimate access. The test oracle must come from the product's access policy, not from assumptions about role names.

Test every object reference and action

Inventory client-controlled references in path and query parameters, JSON fields, GraphQL variables, bulk operations, exports, uploaded-file keys, and asynchronous job identifiers. For each object type, compare an authorized baseline with a controlled negative case using a second test identity. Cover read, update, delete, and actions such as sharing or exporting where those actions exist.

For each case, retain only the evidence needed to reproduce the decision:

  1. Test identity, role, tenant, and fixture owner.
  2. Request method, route, relevant object reference, and action.
  3. Expected policy decision and observed response.
  4. Whether data was returned or state changed; inspect the fixture and relevant audit record.
  5. Any relevant follow-up path, such as a queued job or cache read.

Redact tokens and personal data. Stop if a request reaches a real user record, an unapproved host, or an unexpected state-changing path.

Worked test case: a private document

Assume a fictional workspace where documents are private to their owner unless an explicit share exists. Use test-only accounts and synthetic records:

Principal Relationship to doc-test-A Expected result for read
Alice, tenant North Owner Allow
Ben, tenant North Same tenant, not shared Deny
Cy, tenant South Different tenant Deny
Support role No approved support grant Deny

The expected decisions above are only an example policy; a real test must use the application's documented sharing and support rules. First request doc-test-A as Alice to prove the fixture and route work. Repeat the same read as Ben and Cy, then compare response data and audit events. For an update case, use a harmless fixture field, submit the request as an unauthorized principal, and verify as Alice that the stored value did not change. Do not use an identifier obtained from a real user's account.

The test should assert the security property—not one mandatory HTTP code. A service may return 403 or conceal object existence with 404; either way, it must not disclose protected content or perform the action. Check both the response and the fixture's resulting state.

Follow the authorization decision across system boundaries

An endpoint may make the right decision while a second path bypasses it. Include only paths present in the authorized application scope:

  • Bulk operations: test a mixed set of owned and unowned synthetic objects. Confirm the documented all-or-nothing or per-item behavior and check that rejected items have no side effects.
  • Client-controlled ownership fields: try changing ownerId, tenantId, role, or sharing fields only in a disposable fixture. The server must derive authority from authenticated context and trusted policy, not from a caller-supplied ownership claim.
  • GraphQL: test direct object queries, nested fields, mutations and any in-scope subscriptions. A parent resolver's check does not automatically protect child objects.
  • Files and exports: test the same policy on download keys, export jobs, signed links and generated archives where those functions exist.
  • Caches: confirm a response authorized for one principal cannot be replayed to another principal when policy differs. The cache design may vary; the observable authorization result must not.
  • Queued work: verify that the worker receives trusted actor/tenant context and re-evaluates the necessary permission when it executes. A client-provided job ID or tenant value is not authority.

Role changes, revoked shares and account removal deserve separate cases: an old session or cached decision may outlive the permission that originally allowed access. Test the application's documented invalidation behavior without expanding into unapproved accounts or hosts.

Decisions that change an authorization test

Object ownership is not always a single owner_id comparison. A business policy can permit explicit sharing, delegated access, support access with an approved ticket, or a time-limited role. Record those relationships before the test and use them as the oracle. Do not label a designed sharing path as BOLA merely because two account IDs differ.

Also test the action, not only the object. Permission to read a record may not allow changing, deleting, exporting, or sharing it. A user may be allowed to update a profile field but not transfer the record to another tenant. For each action, state which fields the client may change and which values the server must derive.

When the application returns a partial result for a list or bulk request, verify the contents and side effects for each fixture. A generic success response can conceal a per-item authorization failure—or a successful unauthorized write.

Example server-side authorization boundary

Illustrative pseudocode—not a framework-specific implementation:

document = documentRepository.findForAction(
    objectId = request.documentId,
    principal = authenticatedPrincipal,
    action = "read"
)

if document is absent:
    return the application's documented deny response

return document

The repository or policy layer must apply the complete access rule, including any sharing or delegated-access model. Reuse the same policy for reads and writes, but pass the requested action explicitly; a read grant must not silently become an update grant.

Frequently asked questions

Is BOLA different from IDOR?

IDOR describes a user-controlled reference that points to an object. BOLA describes the missing object-level authorization decision that lets the caller access or change that object. In API discussions, the terms are often used for the same class of flaw; describe the actual failed policy rather than relying on the label.

Does using UUIDs prevent BOLA?

No. Unpredictable identifiers can reduce guessing, but authorization must still be checked when a caller supplies a valid identifier.

Must every denied request return 403?

No. The application may use 403 or 404 according to its disclosure policy. The important checks are that protected data is not returned, the operation does not occur, and the response matches the documented policy.

Can an automated scanner confirm BOLA by itself?

Not generically. Automated coverage depends on usable test identities, known object ownership, application-specific policy and an oracle for expected results. A tool observation still needs review against those facts.

Remediate at the object-owning boundary

Enforce policy on the server for every object and action, using the authenticated principal and trusted server-side relationships. A data-access query can scope candidate records by tenant or owner, but that filter must reflect the full policy; tenant equality alone may be insufficient where roles, sharing, or delegated access exist. Do not trust a client-supplied ownerId or tenantId as proof of authority.

Add regression tests for both sides of the rule: the permitted owner or role still succeeds, while the unauthorized identity is denied and no state change occurs. Include relevant bulk and asynchronous paths. Prefer a consistent authorization service or domain policy over scattered checks that can drift between endpoints.

Common testing mistakes

  • Treating an unpredictable ID as a substitute for authorization.
  • Inferring impact from 200 OK without checking returned data or side effects.
  • Testing only one role, one tenant, or read access.
  • Using production accounts or real customer records without explicit authorization.
  • Treating every 403 as a pass without verifying that no write already occurred.
  • Claiming a scanner always misses BOLA or can fully validate it; coverage depends on the application's identity, policy, and test configuration.

What a defensible finding should contain

Describe the affected object type and action, the two test identities and their expected relationship, the policy that should apply, the redacted request and response, and the observed unauthorized result. State limitations and whether a write or downstream effect was verified. Do not include secrets, customer records, or an exploit claim beyond the evidence.

For remediation, define a repeatable acceptance condition: an authorized identity can complete the intended flow; an unauthorized identity cannot read or change the fixture; and the relevant regression test passes. See our remediation and retest workflow for a closure record and the GraphQL security guide for resolver-specific cases. Teams planning an authorized API review can also explore Eresus Security's API security assessment.

Sources

AI Security Starter Training

Request a practical checklist for prompt injection, RAG data leakage, MCP risks, and model-file security before launch.

Prompt injection and guardrail bypass checks.
RAG data leakage and permission-boundary review.
MCP identity, transport, and command-risk controls.

No spam. Used only to send the resource and related security notes.

Related Research

Related Services

Scope Estimator

Get a rough engagement size before the scoping call.

Estimated engagement

5–7 days

Request exact scope