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.
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.
- Verify principal
Validate issuer, audience, expiry, and credential context.
- Derive tenant
Use trusted identity or workload context, never a client tenant header.
- Load scoped object
Query within tenant and relationship scope.
- Evaluate action
Apply ownership, policy, and contextual restrictions.
- 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.
| Layer | Required control | Common bypass |
|---|---|---|
| API handler | Object and action check | Route alias or alternate API version |
| Data access | Tenant-scoped query or row-level defence | Unscoped repository method |
| Cache | Tenant-aware eligibility and key | Cached decision survives membership change |
| Worker or export | Same policy context | Async job trusts submitted object ID |
| Gateway | Token and coarse route policy | Gateway assumed to know object relationship |
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=passTest 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_idsupplied 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.
Related guides
Authoritative references
- OWASP API1:2023 Broken Object Level Authorization
- OWASP Authorization Cheat Sheet
- OWASP Multi-Tenant Security Cheat Sheet
- NIST SP 800-162: Attribute Based Access Control
- RFC 9700: OAuth 2.0 Security Best Current Practice
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