Preventing Broken Object Level Authorization (BOLA/IDOR) in APIs
AI generated
OWASP
0x00
OWASP API Security Top 10 · A01
Preventing Broken Object Level Authorization (BOLA/IDOR) in APIs
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.

14 min read OWASP API Top 10 Access Control

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.

11. FAQ: BOLA/IDOR

1What is the difference between BOLA and IDOR?
IDOR describes the vulnerable direct object reference from an attacker's perspective, while BOLA is the broader OWASP term for the missing object level authorization check behind it. In practice the two terms are mostly used interchangeably.
2Does a UUID instead of a sequential ID provide protection?
No. A UUID makes guessing foreign IDs harder, but it does not replace an authorization check. Once a UUID becomes known through another channel, access without a check is just as possible as with a sequential number.
3Why is a valid login token not enough?
A token only confirms who the user is, not whether they are authorized for a specific object. Authorization must be checked separately and per object.
4How do you find BOLA flaws in an existing API?
With a two-account test: create an object as user A and try to fetch, modify, or delete it using the valid token of user B. If access is granted, a BOLA flaw exists.
5Are only GET endpoints affected?
No, PATCH, PUT, and DELETE are equally affected, and in practice checked even less often since teams tend to focus on securing read operations first.
6How do you implement the check cleanly in Symfony?
Through the Voter system and a denyAccessUnlessGranted call right after loading the object, instead of repeating scattered checks across individual controllers.
7Can a role system alone prevent BOLA?
No. A role system usually only verifies whether a user can access a resource type in general, not whether they own the specific object. Both checks are needed.
8How should nested resources be handled?
For nested paths, verify that both IDs actually belong together, meaning the task really belongs to the given project and the user has access to that project.
9How often should an API be tested for BOLA?
Ideally automated with every build through CI tests, plus manually whenever a new or changed endpoint with an ID parameter ships.
10What role does logging play for BOLA?
Logging failed authorization checks helps catch systematic attacks like ID incrementing early, before large amounts of data can be exfiltrated.