Guides

API security guide

Preventing BOLA and Authorization Failures in APIs

An authenticated caller, valid token, and route-level permission do not prove that the caller may act on the specific object in the request.

Published
Updated
Reading time
13 min read
On this page

Broken object level authorization, also called IDOR in many application contexts, occurs when a caller can alter an object reference and read, change, or delete a record without the required relationship. A WAF, rate limit, or API gateway can reduce abuse and validate coarse credentials; it is not normally the authority for the current relationship between caller A and object B.

Overview

Outcome

Make object-level and tenant-scoped checks a release invariant across API routes, background work, storage, caches, and alternate service paths.

Make the authorisation question explicit

Every object-bearing request needs a verified principal, requested action, specific object, tenant, and relevant context. Default deny, then grant through explicit ownership, membership, delegated relationship, role, or policy. Function-level permissions and object-level authorisation are complementary, not interchangeable.

Object authorisation decision
  1. Verify principal

    Validate issuer, audience, expiry, and credential context.

  2. Derive tenant

    Use trusted identity or workload context, never a client tenant header.

  3. Load scoped object

    Query within tenant and relationship scope.

  4. Evaluate action

    Apply ownership, policy, and contextual restrictions.

  5. Record decision

    Log safe evidence and return a consistent outcome.

The service and data path must evaluate the relationship to the requested object after identity is verified and before data is returned or changed.

Tenant-scope every access path

Scope database queries, list endpoints, bulk actions, caches, object-storage paths, queues, search indexes, GraphQL resolvers, and background consumers. UUIDs, signed IDs, and opaque URLs reduce easy enumeration but are never authorisation. A caller who obtains a valid identifier must still be denied without the relationship.

LayerRequired controlCommon bypass
API handlerObject and action checkRoute alias or alternate API version
Data accessTenant-scoped query or row-level defenceUnscoped repository method
CacheTenant-aware eligibility and keyCached decision survives membership change
Worker or exportSame policy contextAsync job trusts submitted object ID
GatewayToken and coarse route policyGateway assumed to know object relationship
Representative BOLA test result
principal=tenant-A:user-1 action=read object=tenant-B:invoice-472
api_status=404 decision=deny policy_version=2026.07
cache_state=bypass audit_event=recorded
cross_tenant_regression=pass

Test adversarially before release

Build an access-control matrix with at least two identities in one tenant and two tenants. For every object-bearing API, test read, create, update, delete, export, download, list, filter, pagination, batch, GraphQL, and asynchronous operations. Mutate object identifiers in paths, query parameters, body fields, nested objects, and signed URLs. A release should fail if a cross-user or cross-tenant operation succeeds.

Use 401 for absent or invalid authentication and 403 for an authenticated caller without permission. A consistent 404 can be an intentional design to hide object existence, but it must not mask an allow decision or make incident investigation impossible.

Log decisions and prepare containment

Record timestamp, request ID, verified principal or workload identity, tenant, action, resource type, safe object reference, policy version, decision reason, and source service. Do not log tokens or full sensitive payloads. On suspected exposure, disable the vulnerable action, revoke affected sessions or keys, preserve evidence, identify affected tenants and objects, fix every alternate path, invalidate related caches and signed URLs, and add regression tests.

Troubleshooting

BOLA prevention failures

  • Trusting a tenant_id supplied by the caller or forwarding it without a protected proxy boundary.
  • Treating JWT scope as permission to every object of a type.
  • Applying checks in an HTTP handler but not exports, workers, GraphQL resolvers, or storage URLs.
  • Using WAF blocks or rate limits to mask a successful unauthorised request.

Authoritative references

Make tenant isolation an API invariant

Optimi can help connect route controls, origin authorisation, safe telemetry, and edge protection without pretending the edge owns object permissions.

Review API authorisation