The most common API vulnerability, explained
BOLA and IDOR top the OWASP API Security Top 10 because being logged in does not automatically mean being authorized to access a specific object. We explain how the gap appears, walk through an attack, and lay out a systematic way to audit existing APIs against it.
Table of Contents
- 1. What Is Broken Object Level Authorization?
- 2. Why Authentication Alone Is Not Enough
- 3. Common Hotspots in Real APIs
- 4. Risks and Consequences of a BOLA Incident
- 5. A Systematic Audit Strategy for Existing APIs
- 6. Automated Testing and Tooling
- 7. Implementing Object Level Authorization Checks
- 8. Common Mistakes in Implementation
- 9. Best Practices and Checklist
- 10. Summary
- 11. FAQ
1. What Is Broken Object Level Authorization?
Broken Object Level Authorization, or BOLA, describes a flaw where a server correctly checks that a user is logged in, but never checks whether that user is actually allowed to access the specific object being requested. The closely related term Insecure Direct Object Reference, or IDOR, describes the same effect from an attacker's point of view: a direct object reference, usually an ID in the URL or request body, gets passed straight through without any ownership check.
BOLA has consistently ranked first in the OWASP API Security Top 10 for years, largely because APIs tend to expose the internal structure of resources directly through endpoints such as /api/orders/{id} or /api/invoices/{id}. Every sequential or guessable ID becomes a potential attack vector the moment the per-object authorization check is missing.
2. Why Authentication Alone Is Not Enough
Authentication answers the question of who someone is. Authorization answers the question of what that person is allowed to do, tied to a specific resource. Many developers conflate the two, or assume that a valid token automatically carries object level rights with it. That confusion is exactly the root cause of BOLA: an attacker owns a perfectly valid account and a perfectly valid token, and simply changes the ID in a request to reach someone else's object.
The example below shows a Symfony controller serving order data. The vulnerable version only checks that a user is logged in at all. The fixed version additionally verifies that the order actually belongs to the current user before any data is returned.
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Entity\Order;
use App\Repository\OrderRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
final class OrderApiController extends AbstractController
{
public function __construct(
private readonly OrderRepository $orderRepository,
) {
}
// Vulnerable: any logged-in user can fetch any order by ID
#[Route('/api/orders/{id}', methods: ['GET'])]
public function showVulnerable(int $id): JsonResponse
{
$order = $this->orderRepository->find($id);
if (!$order instanceof Order) {
throw $this->createNotFoundException();
}
return $this->json($order, 200, [], ['groups' => 'order:read']);
}
// Fixed: explicit per-object authorization check before returning data
#[Route('/api/orders/{id}', methods: ['GET'])]
public function showSecured(int $id): JsonResponse
{
$order = $this->orderRepository->find($id);
if (!$order instanceof Order) {
throw $this->createNotFoundException();
}
$this->denyAccessUnlessGranted('VIEW', $order);
return $this->json($order, 200, [], ['groups' => 'order:read']);
}
}
3. Common Hotspots in Real APIs
BOLA rarely shows up where teams look first. Classic hotspots include detail endpoints for individual resources such as invoices, orders, messages, or profiles, file downloads referenced by a numeric or UUID based ID, and PATCH or DELETE endpoints, where the write path is checked even less often than the read path. Nested resources are equally exposed, for example /api/projects/{projectId}/tasks/{taskId}, when only the existence of the task is verified, not its relationship to the project and the current user.
Internal admin or support APIs are particularly tricky: they were often built for a handful of staff members and secured loosely as a result, then later exposed through a frontend or a mobile app to a much larger user base without the authorization logic ever being tightened up.
4. Risks and Consequences of a BOLA Incident
A successful BOLA attack typically enables systematic retrieval of other users' records simply by incrementing an ID. When personal data is involved, that means a reportable data breach under GDPR, and when payment or contract data is involved, it adds serious financial and legal exposure on top. Because BOLA flaws can often be exploited with simple automated scripts, the potential damage is not limited to a handful of records, it can cover an entire resource's dataset.
Beyond the direct data loss, there is usually reputational damage, since affected customers need to be notified and the incident tends to become public. Regulators increasingly treat missing object level access control as gross negligence, which raises the fine risk considerably.
5. A Systematic Audit Strategy for Existing APIs
Auditing an existing API systematically starts with inventorying every endpoint that accepts an ID in the path, in query parameters, or in the body. For each of these endpoints, work with two test accounts: user A creates a resource, and user B, using their own valid token, tries to read, modify, or delete the resource that belongs to user A.
Run this check separately for every HTTP method, since GET endpoints are often better protected than PATCH, PUT, or DELETE endpoints. It also pays to test with IDs outside the expected range and with IDs belonging to deleted or archived objects, since soft delete logic tends to skip the authorization check entirely.
6. Automated Testing and Tooling
Manual spot checks stop scaling once an API grows. A useful pattern is a test suite building block that automatically generates a negative test for every new endpoint with an ID parameter: create an object as user A, fetch it as user B, expect an HTTP 403 or 404. Wired into the CI pipeline, this reliably fails the build the moment a new endpoint ships without an object level check.
Specialized API security scanners can also read an OpenAPI specification and generate BOLA test cases automatically. These tools do not replace manual review of complex authorization logic, but they are a solid first filter for catching obvious gaps early in development.
7. Implementing Object Level Authorization Checks
In Symfony, the Voter system is a natural fit for keeping authorization logic centralized and testable instead of rewriting it in every controller. A voter receives the concrete object and the current user and decides, based on clear rules, whether access is allowed, for example because the user owns the resource or belongs to an authorized group.
The key is that this check must not be scattered optionally through business logic. It belongs in a single, mandatory place right after the object is loaded, ideally enforced by a test that fails whenever a denyAccessUnlessGranted call is missing.
8. Common Mistakes in Implementation
A frequent mistake is enforcing authorization only in the frontend and trusting that the UI never shows a foreign ID. Since the API remains directly reachable, an attacker bypasses that check trivially. Another common mistake is implementing the check only for the primary resource type while forgetting nested or related objects such as comments, attachments, or history entries.
UUIDs are also frequently misunderstood as sufficient protection on their own. A UUID is hard to guess, but it does not prevent a BOLA attack if the ID becomes known through another channel, such as a shared link, server logs, or a prior, legitimate interaction with the same object.
9. Best Practices and Checklist
Every API route with an ID parameter needs an explicit, object level authorization check after the resource is loaded and before any data is returned or modified. That check belongs in a centralized, reusable component such as a voter or a policy class, not scattered across if statements.
It also helps to log failed authorization checks, so that attack patterns like systematic ID incrementing surface early, and to repeat the audit strategy described above with every release that adds or changes endpoints.
| Attack Vector | Example Endpoint | Typical Risk | Mitigation |
|---|---|---|---|
| Direct ID in the URL | /api/orders/{id} | Foreign order data exposed | Object level check via voter |
| ID in the request body | PATCH /api/profile with userId | Foreign profile modified | Server-side ownership check, not client value |
| Nested resource | /api/projects/{p}/tasks/{t} | Task readable without project link | Verify both IDs belong together |
| Soft-deleted object | /api/invoices/{id} (archived) | Authorization skipped | Check independent of resource status |
| Admin API reachable via app | /api/internal/users/{id} | Missing role separation | Separate authorization layer for internal routes |
Mironsoft
Security audits, OWASP-compliant hardening, and secure architecture
Applications that actually hold up against a real attack attempt?
We review existing applications for classic OWASP vulnerabilities, insecure authentication, and missing input validation, then build an architecture that structurally reduces attack surface instead of just patching individual symptoms.
Security Audit
Systematically checking OWASP Top 10, auth flows, and input validation for vulnerabilities.
Secure Architecture
Building rate limiting, encryption, and access controls correctly from the ground up.
Incident Readiness
Establishing logging, monitoring, and response processes for when things go wrong.
10. Summary
BOLA/IDOR
Root Cause
Missing object level check despite valid authentication.
Detection
Two-account test per endpoint and HTTP method.
Fix
Centralized voter or policy check after every object load.
Prevention
CI tests, logging of failed checks, regular audits.