Broken Access Control: Why It Dominates OWASP Top 10 and How to Fix It

· Emma Rodriguez · 10 min read

Broken access control has held the number one position in the OWASP Top 10 since 2021, displacing injection vulnerabilities that had dominated the list for over a decade. The reason is straightforward: modern web applications have become more complex, APIs have proliferated, and authorization logic has grown far beyond simple role checks. Every API endpoint that returns user-specific data, every admin panel behind a route guard, and every file download link that includes an object identifier is a potential access control failure point. For organizations operating web infrastructure behind CDN and edge proxy layers, understanding where access control enforcement belongs in the request processing pipeline is the difference between defense in depth and a false sense of security.

What is broken access control?

Broken access control occurs when an application fails to properly enforce restrictions on what authenticated users are allowed to do. An access control vulnerability lets a user perform actions or access data outside their intended permissions. Unlike injection attacks that exploit input handling flaws or cryptographic weaknesses that target data protection, broken access control exploits gaps in the authorization logic that governs who can do what with which resources.

The OWASP classification identifies several distinct sub-categories of broken access control. Vertical privilege escalation occurs when a regular user accesses administrator functionality. Horizontal privilege escalation occurs when one user accesses another user's data at the same privilege level. BOLA (Broken Object Level Authorization) occurs when the application does not verify that the requesting user is authorized to access a specific object. IDOR (Insecure Direct Object Reference) occurs when the application exposes internal identifiers that users can manipulate to access unauthorized objects. Each sub-category requires different detection methods and different mitigation strategies.

Types of Broken Access Control Vulnerabilities Broken Access Control Vulnerability Taxonomy Broken Access Control (A01) Vertical Escalation User → Admin access Forced browsing to /admin, /api/admin/* Horizontal Escalation User A → User B data Same role, different ownership scope BOLA / IDOR /api/orders/{id} Manipulate object ID to access others' data Method Tampering GET allowed but DELETE/PUT not checked HTTP verb override Common Attack Patterns Vertical: GET /admin/users → 200 OK (no role check in handler) Horizontal: GET /api/users/42/profile → user 99 sees user 42's data BOLA: GET /api/invoices/1001 → sequential ID enumeration reveals all invoices Method: DELETE /api/posts/55 → any user can delete any post (only GET was authorized)
Figure 1: Taxonomy of broken access control vulnerabilities with common attack patterns for each category

Why broken access control is difficult to prevent

Access control is uniquely challenging because it cannot be solved by a single technology or tool. Unlike SQL injection (parameterized queries), XSS (output encoding), or CSRF (anti-forgery tokens), there is no universal countermeasure for broken access control. Every authorization decision depends on the application's data model, business rules, and user relationship structure. An e-commerce platform's access rules differ fundamentally from a healthcare system's access rules, and both differ from a multi-tenant SaaS application's access rules.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

The complexity compounds in microservice architectures where authorization decisions span multiple services. A request to update a document may pass through an API gateway, a document service, a permissions service, and a storage service. Each service must independently verify that the requesting user is authorized, because any service that trusts upstream authorization without performing its own check creates a bypass opportunity. If the API gateway validates the JWT token but the document service does not verify document ownership, an attacker who obtains a valid token for one account can access documents belonging to any account.

Another reason broken access control persists is the gap between authentication and authorization. Many applications correctly implement authentication (verifying who the user is) but inadequately implement authorization (verifying what the user is allowed to do). A web application firewall can verify that a request carries a valid authentication token, but the WAF cannot determine whether user 42 should be allowed to access invoice 1001. That decision requires knowledge of the relationship between users and invoices that exists only in the application's database.

The PDP/PEP architecture pattern

The most effective architectural approach to access control separates authorization logic into two components: the Policy Decision Point (PDP) and the Policy Enforcement Point (PEP). The PDP is a centralized service that evaluates authorization requests against a set of policies and returns allow/deny decisions. The PEP is a component embedded in each service (or at the API gateway) that intercepts requests, queries the PDP, and enforces the decision.

PDP/PEP Authorization Architecture Policy Decision Point / Policy Enforcement Point Architecture Client User request CDN Edge PEP Token validation Scope/role check Rate limiting IP/Geo blocking Gateway PEP Route authorization Method enforcement Claim extraction PDP query Service PEP Object-level authz Ownership verification Field-level filtering DB query scoping Policy Decision Point OPA / Cedar / Casbin Evaluates policies against context Returns: allow / deny + reason Policy Store Git-versioned .rego/.cedar External Data User roles, team membership Request flow Authorization query Data/policy fetch Each PEP layer handles different authorization granularity
Figure 2: Three-tier PDP/PEP architecture with CDN edge enforcement, API gateway enforcement, and service-level object authorization

The PDP/PEP separation provides three critical benefits. First, authorization logic is centralized in the PDP, which means policy changes take effect across all services without modifying or redeploying application code. Second, the PDP can be tested independently using unit tests that validate policy behavior against specific inputs. Third, the PDP creates a single audit point where all authorization decisions are logged, enabling security monitoring and compliance auditing.

In a CDN-backed architecture, PEPs operate at three layers with increasing authorization granularity:

Policy-as-code with OPA and Cedar

Policy-as-code replaces ad-hoc authorization logic scattered throughout application code with declarative policies written in a domain-specific language. The two most widely adopted policy engines are OPA (Open Policy Agent) with its Rego language, and Cedar, developed by Amazon for its Verified Permissions service.

OPA evaluates policies written in Rego, a declarative query language designed for policy evaluation. An OPA policy takes a structured input (the authorization request) and produces a structured output (the authorization decision). The following Rego policy implements BOLA prevention by verifying that the requesting user is the owner of the requested resource:

# policy/api_authz.rego
package api.authz

import rego.v1

default allow := false

# Allow users to access their own resources
allow if {
    input.method == "GET"
    input.path = ["api", "users", user_id, _]
    input.user.id == user_id
}

# Allow users to access resources they own
allow if {
    input.method in {"GET", "PUT", "DELETE"}
    input.path = ["api", "documents", doc_id]
    data.documents[doc_id].owner == input.user.id
}

# Allow admins to access any resource
allow if {
    input.user.role == "admin"
}

# Allow team members to access team resources
allow if {
    input.path = ["api", "teams", team_id, _]
    input.user.id in data.teams[team_id].members
}

Cedar, developed by Amazon, uses a permit/forbid model with a syntax designed to be readable by non-engineers. Cedar policies are statically analyzable, meaning the Cedar engine can mathematically prove that a set of policies never produces conflicting decisions. The following Cedar policy expresses the same BOLA prevention logic:

// Allow users to access their own documents
permit (
  principal,
  action in [Action::"ReadDocument", Action::"UpdateDocument"],
  resource
) when {
  resource.owner == principal
};

// Allow team members to access team documents
permit (
  principal,
  action == Action::"ReadDocument",
  resource
) when {
  principal in resource.team.members
};

// Deny all other access by default (implicit in Cedar)

The choice between OPA and Cedar depends on the deployment context. OPA runs as a sidecar or daemon process and can be deployed at the edge computing layer for low-latency policy evaluation. Cedar provides stronger formal verification guarantees and integrates tightly with Amazon's IAM ecosystem. Both engines support policy testing, version control, and hot-reloading of policy changes without service restarts.

CDN and WAF layer access control enforcement

While the application layer must handle fine-grained authorization, the CDN and WAF layer provides an essential first line of defense that blocks the most common access control attacks before they reach the origin server.

Forced browsing protection

Forced browsing attacks attempt to access administrative endpoints or restricted resources by directly requesting their URLs. A CDN security policy should define explicit allow-lists for sensitive URL patterns. The following nginx configuration at the CDN edge blocks forced browsing to administrative endpoints:

# Block direct access to admin endpoints without valid admin token
location ~ ^/(admin|internal|debug|api/admin)/ {
    # Require Authorization header
    if ($http_authorization = '') {
        return 401;
    }

    # Validate JWT claims at edge (requires ngx_http_auth_jwt_module)
    auth_jwt "admin_realm";
    auth_jwt_key_file /etc/nginx/jwt_keys/public.pem;

    # Check for admin role claim
    if ($jwt_claim_role != "admin") {
        return 403;
    }

    proxy_pass https://origin-backend;
}

# Block access to internal service endpoints
location ~ ^/(metrics|health|internal-api)/ {
    # Only allow internal network
    allow 10.0.0.0/8;
    allow 172.16.0.0/12;
    deny all;
}

HTTP method restriction

Many access control vulnerabilities arise from failure to restrict HTTP methods. An endpoint that correctly authorizes GET requests may not check authorization for DELETE or PUT requests. The CDN edge can enforce method restrictions as a defense-in-depth measure:

# Restrict methods on read-only API endpoints
location ~ ^/api/public/ {
    limit_except GET HEAD OPTIONS {
        deny all;
    }
    proxy_pass https://origin-backend;
}

# Restrict methods on user-facing content
location ~ ^/api/users/\d+/profile$ {
    limit_except GET PUT {
        deny all;
    }
    proxy_pass https://origin-backend;
}

Sequential ID enumeration prevention

BOLA attacks often exploit sequential numeric IDs by iterating through /api/users/1, /api/users/2, /api/users/3 to enumerate resources. The CDN bot detection system should flag rapid sequential requests to parameterized endpoints as suspicious. Threat intelligence feeds can identify known enumeration tools and block their user-agent signatures at the edge.

Access Control LayerWhat It PreventsLimitation
CDN Edge (WAF rules)Forced browsing, method tampering, IP-based blockingCannot check object ownership
CDN Edge (JWT validation)Expired tokens, missing scopes, invalid signaturesCannot check resource-specific permissions
API Gateway (PEP)Route-level authorization, role-based accessCannot check relationships between user and object
Service Handler (PEP + PDP)Object-level authorization, ownership, team membershipRequires database access for every check
Database (row-level security)Unauthorized data access even through SQL injectionComplex to configure, database-vendor specific

How does Fine-Grained Authorization (FGA) address BOLA?

Fine-Grained Authorization models authorization as a graph of relationships between users and resources. Inspired by Google's Zanzibar paper, FGA systems evaluate authorization by traversing a relationship graph rather than checking static role assignments. A user has access to a document not because they hold a "document-reader" role, but because they are a member of the team that owns the document, or because the document's owner explicitly shared the document with them.

The relationship model uses tuples of the form (user, relation, object). For example: (user:alice, editor, document:readme) means Alice is an editor of the readme document. (team:engineering, parent, org:acme) means the engineering team belongs to the Acme organization. Authorization checks traverse these relationships: "Can Alice view document X?" becomes a graph traversal that checks whether Alice is a direct viewer of X, or an editor of X (editors can view), or a member of a team that owns X.

Fine-Grained Authorization Relationship Graph FGA Relationship Graph: Authorization by Graph Traversal Alice user Bob user Carol user Engineering team Marketing team Acme Corp organization API Spec document Campaign Plan document Budget Sheet document member member admin owner owner viewer (shared) Check: Alice → API Spec? ALLOW (via team) Bob → API Spec? DENY
Figure 3: FGA relationship graph showing how authorization is determined by traversing user-team-resource relationships rather than static roles

FGA directly prevents BOLA because every resource access requires a relationship check. When user Bob requests /api/documents/api-spec, the FGA engine checks whether any relationship path connects Bob to the API Spec document. Since Bob is a member of the Marketing team, which does not own the API Spec document, and no direct sharing relationship exists, the access is denied. This check happens automatically for every resource access, eliminating the class of bugs where developers forget to add an ownership check to a new endpoint.

Database-level access control as the last line of defense

Row-level security (RLS) policies in the database provide the final enforcement layer. Even if all application-level authorization checks are bypassed through a code vulnerability or a SQL injection attack, RLS policies prevent unauthorized data access at the database engine level.

PostgreSQL's RLS feature allows defining policies that automatically filter rows based on the current session context. The following configuration ensures that each user can only query their own records:

-- Enable row-level security on the orders table
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- Users can only see their own orders
CREATE POLICY user_orders_policy ON orders
    FOR ALL
    USING (user_id = current_setting('app.current_user_id')::uuid);

-- Set the current user context on each request
-- (called by the application before executing queries)
SET app.current_user_id = 'user-uuid-here';

-- This query automatically filters to the current user's orders
SELECT * FROM orders WHERE total > 100;
-- RLS adds: AND user_id = 'user-uuid-here'

RLS policies do not replace application-level authorization. They serve as a safety net that catches authorization failures at the data layer. The performance impact of RLS is minimal because the database optimizer incorporates the security filter into the query plan. For applications handling compliance-sensitive data, RLS provides auditable, database-enforced access control that cannot be bypassed by application bugs.

Monitoring and detecting access control violations

Detecting access control violations requires log analysis that identifies patterns of unauthorized access attempts. The monitoring system should track and alert on the following indicators:

The CDN incident response playbook should include automated responses for confirmed access control violations: blocking the offending IP at the edge, revoking the compromised user's session tokens, and notifying the security team. The DDoS protection infrastructure can be leveraged to apply emergency rate limits when an active enumeration attack is detected.

Frequently asked questions

1. What is the difference between BOLA and IDOR?

BOLA (Broken Object Level Authorization) and IDOR (Insecure Direct Object Reference) describe the same class of vulnerability from different perspectives. IDOR focuses on the symptom: the application exposes internal object identifiers (such as database IDs) in URLs or API parameters, and users can manipulate these identifiers to access objects belonging to other users. BOLA focuses on the root cause: the application fails to verify that the authenticated user is authorized to access the specific object they requested. BOLA is the preferred term in modern API security because it emphasizes the missing authorization check rather than the identifier exposure.

2. Can a CDN or WAF prevent broken access control?

A CDN or WAF can mitigate some access control attacks but cannot fully prevent broken access control. WAF rules block known attack patterns like path traversal, forced browsing to admin endpoints, and HTTP method manipulation. CDN edge functions can validate JWT tokens and enforce coarse-grained authorization (checking that a valid token exists and has the required scope). However, fine-grained object-level authorization (checking that user A is allowed to access document 12345) requires application-level logic with access to the data model. The CDN layer provides defense in depth by blocking common attack patterns before they reach the origin.

3. What is policy-as-code and how does it improve access control?

Policy-as-code defines authorization rules in a declarative policy language (such as OPA Rego or Cedar) rather than embedding authorization logic directly in application code. The policy engine runs as a separate service that the application queries for authorization decisions. Authorization logic is version-controlled and auditable, policies can be tested independently, the same policies apply consistently across multiple services, and policy changes do not require application redeployment. OPA and Cedar are the two most widely adopted policy-as-code engines.

4. How does Fine-Grained Authorization differ from RBAC?

Role-Based Access Control (RBAC) assigns permissions to roles, and users are assigned to roles. RBAC decisions are based on who the user is (their role). Fine-Grained Authorization (FGA) makes decisions based on the relationship between the user and the specific resource. FGA answers questions like "Is user A a member of the team that owns document X?" that RBAC cannot express without creating an impractical number of roles. FGA systems model authorization as a graph of relationships between users and objects, enabling checks that consider ownership, team membership, organizational hierarchy, and sharing permissions.

5. What are the most common access control mistakes in API design?

The five most common access control mistakes in API design are: (1) relying on client-side enforcement without server-side checks, (2) using sequential numeric IDs that are easy to enumerate, (3) checking authentication (is the user logged in?) without checking authorization (is this user allowed to access this specific resource?), (4) applying authorization checks in API gateway middleware but not in the service handler, creating bypass opportunities through internal service calls, and (5) failing to restrict HTTP methods, allowing users to perform DELETE or PUT operations on resources they can only GET.

External References

References & Standards: Cloudflare documentation · OWASP CDN Security