
A user can be legitimately signed in and still request a resource they must not access. Broken object-level authorization, often shortened to BOLA, describes a failure to enforce that resource-specific boundary. In a multi-tenant application, the question is not simply whether the request contains a valid identity. The application also needs to establish whether that identity may perform the requested action on the selected object in the relevant organization.
The examples in this guide use an imaginary invoice service with two organizations. They are design and testing exercises for systems you own or are authorized to assess. Start with the API security foundation for the broader context, then use this workflow to make one sensitive resource boundary explicit before applying the pattern elsewhere.
Describe the permission before writing the query
Write an authorization sentence with four parts: actor, action, resource, and context. For example, an active billing member may read invoices belonging to their active organization. That sentence says more than a role check. It includes membership state, a relationship to a particular organization, and the ownership of the invoice. It also separates reading from more consequential actions such as refunding or changing payment details.
Make ownership explicit
Create separate sentences for exceptions. A support employee who may investigate invoices under an approved support process should not inherit an undocumented global bypass. Specify when that access is appropriate, what approval is required, and what evidence is recorded. Making the exceptions visible early reduces the temptation to solve a difficult case by granting a broad role that persists long after the original need disappears.
Establish tenant context from a trusted boundary
A client may need to select an organization, but the selection itself is not proof of membership. Resolve the authenticated actor and verify their relationship to the selected tenant before using that tenant as an access boundary. Decide how a person with multiple memberships changes context, how removed memberships take effect, and how service identities differ from human users.
Keep this context consistent across the request. A header, path parameter, and body property should not quietly point at different organizations. Reject conflicting representations or remove unnecessary duplicates from the contract. Where a gateway forwards identity information, ensure the application trusts only an authenticated forwarding path rather than any client-supplied header with a familiar name. Document who owns the validation so neither layer assumes the other performed it.
Scope resource access to the permission
An invoice lookup should reflect both the identifier and the permitted organizational boundary. A design that fetches any invoice first and later relies on scattered conditional checks is harder to review. Prefer a consistent authorization interface that keeps the resource relationship explicit. The exact implementation depends on the data model and framework, but the invariant should be simple: an unauthorized resource must not become an authorized result.
Do not mistake an unguessable identifier for a permission check. Identifiers can appear in links, exported documents, logs, or user conversations. They may reduce accidental discovery, but access should remain correct even when a caller knows the identifier. The OWASP Authorization Cheat Sheet recommends default-deny behavior and permission validation on every request; this guide applies those principles to an illustrative tenant-resource relationship.
Review lists, exports, and nested resources
A detail endpoint may enforce ownership correctly while a list endpoint exposes records from another tenant. Review collections, search filters, counts, pagination, exports, and related-object expansions as independent access paths. A response can reveal information through a title, an aggregate total, or a downloadable file even when the main object body is hidden. Decide which properties and summaries each caller actually needs.
For a nested route such as an invoice with an attachment, verify that the attachment belongs to the authorized invoice as well as the tenant. Checking that both identifiers exist independently does not establish their relationship. Include batch operations in the same review. Define whether a mixed authorized-and-unauthorized batch is rejected entirely or processed with per-item outcomes, and ensure the behavior does not expose unauthorized object details.
Carry the boundary into background work
A request may schedule an export that runs several minutes later. Decide which authorization facts are evaluated at submission and which must be rechecked before execution or download. If membership is revoked while a job waits, the desired outcome should be specified rather than left to whichever credential happens to be stored with the task. Use a scoped worker identity and retain an appropriate reference to the initiating actor.
Review the generated artifact as carefully as the job. Its storage location, access link, lifetime, and delivery mechanism can become a separate path around the original API checks. Avoid assuming that knowing a file URL is always an adequate permission model. Test the download after membership removal and tenant changes. The same reasoning applies to webhooks, report emails, scheduled synchronizations, and other work that outlives the original request.
Build a negative test matrix
Create synthetic organizations with deliberately distinct fixtures. Give each a member, a restricted member, and a resource. Test a legitimate read first, then repeat the action with a different organization's identity, a removed membership, no identity, and an identity with insufficient action permissions. Include the response body and relevant side effects in the assertions, not just the HTTP status.
Keep the tests independent of identifier randomness. The fixture should intentionally provide the other organization's identifier so the test evaluates authorization rather than discovery. Repeat the matrix for write operations, lists, exports, and background tasks. Where the system intentionally masks resource existence, verify consistent behavior. The expected result is defined by the application's access policy; the goal is not to force every endpoint into one universal error-handling pattern.
Inspect caching and privileged pathways
A correctly authorized response can become unsafe if a shared cache serves it to a different caller. Review cache keys, tenant context, response classification, and invalidation behavior. The permission assumptions should survive every layer that stores or reuses data. A performance optimization deserves security tests when it changes how caller-specific results are selected or retained.
Administrative utilities and support tools require similar attention. They often need broader access but should not operate as untracked exceptions. Record the approved scope, restrict who can invoke the tool, and make consequential actions attributable. For emergency access, define both the entry and the expiry. An exception that is technically convenient but operationally invisible weakens the clarity of every carefully reviewed rule around it.
Investigate without exposing more data
When an access check fails, an event can record the policy decision and a protected reference to the resource without reproducing the resource itself. Define a small, consistent vocabulary for failures such as missing membership, insufficient action permission, or mismatched resource scope. Keep externally visible errors appropriately limited while preserving enough restricted internal context for an authorized operator to investigate.
Avoid treating every denied request as confirmed hostile activity. Stale application state, removed memberships, and client bugs can also produce denials. Look for patterns and context before escalating. The security logging guide explains how to retain useful evidence without turning diagnostics into another data disclosure route. Logging supports the access boundary; it does not replace the enforcement that must happen before an action succeeds.
Make ownership checks a maintainable invariant
The most durable result is a resource-access pattern that engineers can recognize, test, and reuse. Keep the tenant relationship visible in service interfaces, code review expectations, and regression fixtures. Whenever a new endpoint or background job touches the resource, make its authorization behavior part of the change rather than an optional later review.
Revisit the invariant when organizations merge, sharing features appear, or support workflows expand. These are legitimate product changes that alter access relationships. A strong multi-tenant API does not rely on the hope that callers request only their own objects. It makes the permitted relationship explicit and preserves it throughout the operation, including asynchronous work and the final returned artifact.
Primary reference: OWASP Authorization Cheat Sheet. The practical workflow and examples above are editorial guidance; the linked reference supplies the relevant technical foundation.