The attacker substituted someone else's identifier in the request, the server returned the data. No exploit, without bypassing the WAF - just GET /api/orders/1254 with someone else's ID. According to Snyk, this is a classic Broken Object Level Authorization scenario, number one in OWASP API Security Top 10. The scale of the consequences is hundreds of millions of dollars in total losses for the company. A similar hole allowed to obtain data from any Peloton user with a simple substrate of user ID in the request, and in the Harbor container registry - to create an administrator account through an unauthorized API call (CVE-2019-16097) Protection APIs from BOLA and IDOR does not start with WAF or rate limiting, but with an authorization architecture within the code. And this Russian-language materials are persistently silent.
BOLA and IDOR: the difference that changes the approach to protection
The terms BOLA and IDOR describe one problem - the lack of verification of the right to access a particular object. But the difference in framery for the developer is fundamental.
IDOR (Insecure Direct Object Reference) appeared in OWASP Web Application Top 10 in 2007. Emphasis - on the mechanism of attack: the application puts outward a predictable identifier, the attacker replaces it. Protection for such a fraising is often reduced to "replace consistent IDs with UUID". And then the problems begin.
As Snyk notes, BOLA clearly recognizes: the problem lives not only in URL parameters. It sits in the bodies of JSON queries, headings, nested object graphs and at the joints of microservices, where the logistics of authorization is implemented in different ways.
For the developer, the conclusion is specific: replacement GET /api/orders/1254 on GET /api/orders/a3f8b2c1-... does not eliminate the broken object level. UUID complicates the cost, but if the server does not check that user_id from JWT - the owner of the order, vulnerability on the spot. UUID leaks through list endpoints, websocket events, data exports and front-end logs. After that, a horizontal increase in privileges is possible again.
OWASP emphasizes in the description API1:2023 Broken Object Level Authorization: a simple comparison of the user_id of the current session with the query parameter is not enough, because this approach covers only a small part of the cases. You need an authorization model that checks the right to access each object for each request.
Three points of authorization failure at the object level
When auditing API contracts, the vulnerability object service bypass surfaces in one of three places. Understanding these points is the basis for systemic prevention of IDOR.
No property check in endpoint
The most common case. Handler gets order_id from the URL, makes a request to the database by only one id and returns the result. There is no binding to the current user. On code review, this is in seconds, but in a project with hundreds of endpoints, holes appear regularly - especially when the new developer copies the existing supervisor and forgets to add a filter to the owner. Exactly OWASP API Security Top 10 It highlights this pattern as the most common.
Distributed logic in microservices
API-gateway validates JWT-token, the order service removes the ID record. The lock confirmed the authentication, the service believes that authorization has already been passed. No one checks the identity of the object. In practice, distributed authorization without a single enforcement point is one of the main causes of incidents with BOLA in production. Authorization is most often broken with API-breaches.
Confidence in customer data
Endpoint POST /api/profile/update accepts user_id from the body of JSON instead of extracting from the server context. The developer has closed the obvious GET /api/users/{id}, but forgot about the less noticeable endpoints, where the identifier hides in the gentest structure of the query. This pattern is the REST API of the authorization vulnerability in its pure form.
Why WAF will not help
Separately about the popular misconception. Traditional and API-aware WAF solutions inspect the query structure, check the diagram and search for known attack patterns (SQLi, XSS, SSRF). But the BOLA-request is syntaxically correct, authenticated, completely valid. The difference between the appeal to your order and someone else is not in the format, but in the bundle of "identity -> object -> permission". The WAF does not model this bundle. Incidents with valid credentials and unauthorized access to data, as a rule, are among the most expensive to detect and eliminate.
Control of access to APIs is closed only in the application’s business logic. Perimeter instruments are useless here.
API authorization patterns: from ownership check to attribute-based access control
Let’s understand specific authorization of API patterns and their applicability to protection from LOA.

Ownership check - minimal pattern. Each request to the resource is filtered by the owner: instead of a "naked" request for id the condition is added AND owner_id = :current_user, where current_user extracted from the server context (JWT-claim, session), and not from the query parameters. Works for simple models "one owner - one resource". It breaks down on shared-resources: a collective document, a common order in the carpool service, the hierarchy "regional administrator -> users of the region".
RBAC (Role-Based Access Control) checks whether the user role is entitled to the type of operation. RBAC ABAC authorization is not mutually exclusive approaches, but layers.
ABAC (Attribute-Based Access Control API) evaluates the attributes of the subject, object, action and context. As Traceable describes, this is the only pattern for complex cases: “a regional administrator can only remove users from its region.” The decision is made by the politician engine, and in the code of the endpoint remains a challenge authorize(subject, action, resource).
Scoped tokens restricts token-level access: JWT contains not only user_id, but also tenant or a set of resources. Endpoint checks the entry of the requested object into the scope. Suitable for tenant-level, less convenient for object-level with a large number of resources.
The principle of preventing IDORs, which I see in real projects: production usually needs a combination - RBAC at the level of functions + ownership check or ABAC at the level of objects.
Policy-as-code: OPA and Rego for centralized API authorization
The main pain of object-level authorization in projects with dozens of microservices is fragmentation. Each service implements a check in its own way, bugs appear with any refactoring. Policy-as-code authorization API solves this by making access rules into a single engine.
OPA (Open Policy Agent) - the most mature tool for this approach (actively supported, CNCF graduated project). Policies are described in the declarative language Rego deployment,-model - sidecar next to the service or a centralized server. Example of Rego Policy for a Carnival Check:
Integration of OPA in CI/CD gives a second line of protection. The pipeline drives the unit tests on Rego-politics: “User A cannot get an order of user B”. If a developer adds a new endpoint without a clear policy, the pipeline is falling. This is policy-as-code literally: there is no policy in the repository - there is no depot.
Alternatives - Casbin (supports RBAC, ABAC, ACL models), Cedar (from AWS, formally verified politician language) and built-in API-gatelicles mechanisms like Kong or Envoy with external authorization. The choice depends on the stack, but the principle is general: authorization lives in a declarative policy that is tested and recurring separately from the business code.
When OPA doesn't fit
OPA adds latency to every request - the value depends on the complexity of the policy, caching and deployment model, so that the benchmark is mandatory for a real load. For high-load APIs with strict latency requirements, this can be tangible. In such cases, policies are compiled in inline-middleware, and OPA is used only to manage and distribute the rules. The complete replacement of inline-checks for calls to the external OPA server can be a bottlenack with a high RPS. Intermediate option - Rego-politics, compiled in WASM-module and performed within the service process. On one project, we did just that - latency fell from 12ms to 0.3ms to a request, and the policies were still stored in Git.
BOLA and IDOR: the difference that changes the approach to protection
The terms BOLA and IDOR describe one problem - the lack of verification of the right to access a particular object. But the difference in framery for the developer is fundamental.
IDOR (Insecure Direct Object Reference) appeared in OWASP Web Application Top 10 in 2007. Emphasis - on the mechanism of attack: the application puts outward a predictable identifier, the attacker replaces it. Protection for such a fraising is often reduced to "replace consistent IDs with UUID". And then the problems begin.
As Snyk notes, BOLA clearly recognizes: the problem lives not only in URL parameters. It sits in the bodies of JSON queries, headings, nested object graphs and at the joints of microservices, where the logistics of authorization is implemented in different ways.
For the developer, the conclusion is specific: replacement GET /api/orders/1254 on GET /api/orders/a3f8b2c1-... does not eliminate the broken object level. UUID complicates the cost, but if the server does not check that user_id from JWT - the owner of the order, vulnerability on the spot. UUID leaks through list endpoints, websocket events, data exports and front-end logs. After that, a horizontal increase in privileges is possible again.
OWASP emphasizes in the description API1:2023 Broken Object Level Authorization: a simple comparison of the user_id of the current session with the query parameter is not enough, because this approach covers only a small part of the cases. You need an authorization model that checks the right to access each object for each request.
Three points of authorization failure at the object level
When auditing API contracts, the vulnerability object service bypass surfaces in one of three places. Understanding these points is the basis for systemic prevention of IDOR.
No property check in endpoint
The most common case. Handler gets order_id from the URL, makes a request to the database by only one id and returns the result. There is no binding to the current user. On code review, this is in seconds, but in a project with hundreds of endpoints, holes appear regularly - especially when the new developer copies the existing supervisor and forgets to add a filter to the owner. Exactly OWASP API Security Top 10 It highlights this pattern as the most common.
Distributed logic in microservices
API-gateway validates JWT-token, the order service removes the ID record. The lock confirmed the authentication, the service believes that authorization has already been passed. No one checks the identity of the object. In practice, distributed authorization without a single enforcement point is one of the main causes of incidents with BOLA in production. Authorization is most often broken with API-breaches.
Confidence in customer data
Endpoint POST /api/profile/update accepts user_id from the body of JSON instead of extracting from the server context. The developer has closed the obvious GET /api/users/{id}, but forgot about the less noticeable endpoints, where the identifier hides in the gentest structure of the query. This pattern is the REST API of the authorization vulnerability in its pure form.
Why WAF will not help
Separately about the popular misconception. Traditional and API-aware WAF solutions inspect the query structure, check the diagram and search for known attack patterns (SQLi, XSS, SSRF). But the BOLA-request is syntaxically correct, authenticated, completely valid. The difference between the appeal to your order and someone else is not in the format, but in the bundle of "identity -> object -> permission". The WAF does not model this bundle. Incidents with valid credentials and unauthorized access to data, as a rule, are among the most expensive to detect and eliminate.
Control of access to APIs is closed only in the application’s business logic. Perimeter instruments are useless here.
API authorization patterns: from ownership check to attribute-based access control
Let’s understand specific authorization of API patterns and their applicability to protection from LOA.

Ownership check - minimal pattern. Each request to the resource is filtered by the owner: instead of a "naked" request for id the condition is added AND owner_id = :current_user, where current_user extracted from the server context (JWT-claim, session), and not from the query parameters. Works for simple models "one owner - one resource". It breaks down on shared-resources: a collective document, a common order in the carpool service, the hierarchy "regional administrator -> users of the region".
RBAC (Role-Based Access Control) checks whether the user role is entitled to the type of operation. RBAC ABAC authorization is not mutually exclusive approaches, but layers.
ABAC (Attribute-Based Access Control API) evaluates the attributes of the subject, object, action and context. As Traceable describes, this is the only pattern for complex cases: “a regional administrator can only remove users from its region.” The decision is made by the politician engine, and in the code of the endpoint remains a challenge authorize(subject, action, resource).
Scoped tokens restricts token-level access: JWT contains not only user_id, but also tenant or a set of resources. Endpoint checks the entry of the requested object into the scope. Suitable for tenant-level, less convenient for object-level with a large number of resources.
The principle of preventing IDORs, which I see in real projects: production usually needs a combination - RBAC at the level of functions + ownership check or ABAC at the level of objects.
Policy-as-code: OPA and Rego for centralized API authorization
The main pain of object-level authorization in projects with dozens of microservices is fragmentation. Each service implements a check in its own way, bugs appear with any refactoring. Policy-as-code authorization API solves this by making access rules into a single engine.
OPA (Open Policy Agent) - the most mature tool for this approach (actively supported, CNCF graduated project). Policies are described in the declarative language Rego deployment,-model - sidecar next to the service or a centralized server. Example of Rego Policy for a Carnival Check:
The service sends to the OPA the context of the request (method, path, user from JWT), OPA returns the solution. The logging of the object level is separated from the business code and is versioned in Git along with the tests.Code:
package api.authz
default allow = false
allow {
input.method == "GET"
input.path = ["api", "orders", order_id]
data.orders[order_id].owner == input.user
}
Integration of OPA in CI/CD gives a second line of protection. The pipeline drives the unit tests on Rego-politics: “User A cannot get an order of user B”. If a developer adds a new endpoint without a clear policy, the pipeline is falling. This is policy-as-code literally: there is no policy in the repository - there is no depot.
Alternatives - Casbin (supports RBAC, ABAC, ACL models), Cedar (from AWS, formally verified politician language) and built-in API-gatelicles mechanisms like Kong or Envoy with external authorization. The choice depends on the stack, but the principle is general: authorization lives in a declarative policy that is tested and recurring separately from the business code.
When OPA doesn't fit
OPA adds latency to every request - the value depends on the complexity of the policy, caching and deployment model, so that the benchmark is mandatory for a real load. For high-load APIs with strict latency requirements, this can be tangible. In such cases, policies are compiled in inline-middleware, and OPA is used only to manage and distribute the rules. The complete replacement of inline-checks for calls to the external OPA server can be a bottlenack with a high RPS. Intermediate option - Rego-politics, compiled in WASM-module and performed within the service process. On one project, we did just that - latency fell from 12ms to 0.3ms to a request, and the policies were still stored in Git.