Broken Access Control

Access control decides what an authenticated (or anonymous) user is allowed to do: which records they can read, which actions they can perform, which admin functions they can reach. When it's missing or wrong, users can view other customers' invoices by changing an ID in the URL, call admin APIs from a regular account, or read another tenant's data in a SaaS product. Broken access control has been #1 on the OWASP Top 10 since 2021, found in a large share of tested applications, and it's #1 on the OWASP API Security Top 10 as Broken Object Level Authorization (BOLA).

It's so common because authorization is application-specific logic. No framework can know that "support agents may view but not refund orders over $500 in other regions". Every endpoint and every query must enforce the rules, and a single missed check is a breach.

TL;DR

Quick Example

A classic IDOR and its fix:

Defense in depth at the database level, with PostgreSQL row-level security:

Core Concepts

Types of Access Control Failures

Authentication vs Authorization

Authentication establishes who the user is. Authorization decides what they may do. Many breaches involve fully authenticated users simply accessing what they shouldn't. Logging in isn't permission.

Authorization Models

Most apps combine RBAC for functions with ownership and tenant checks for objects.

Where to Enforce

Mass Assignment

Frameworks that bind request bodies directly to models let attackers set fields they shouldn't (is_admin, org_id, balance). Use explicit input schemas and allowlists of writable fields (DTOs, Pydantic models, serializer fields) rather than binding entire entities.

Best Practices

Deny by Default

Require authentication for all routes unless explicitly public, and require an explicit permission for every privileged action. New endpoints are then safe by default, instead of open by default.

Don't Rely on Obscurity

Unguessable IDs (UUIDs) reduce enumeration but aren't authorization: IDs leak through logs, referrers, shared links, and other APIs. Always check access. See distributed IDs.

Return 404 for Unauthorized Objects

For resources the user shouldn't know about, respond as if they don't exist, so attackers can't confirm valid IDs. Use 403 when the user legitimately knows the resource exists but lacks permission for the action.

Log and Monitor Authorization Failures

Repeated 403s and 404s across sequential IDs signal probing. Log denials with user, resource, and action, alert on anomalies, and rate-limit. See audit logging.

Test Authorization Like a Feature

Maintain automated tests for each role and ownership rule: user A vs user B, tenant X vs tenant Y, regular user vs admin, across every endpoint. Tools like Burp's Autorize and API security scanners help find gaps.

Common Mistakes

Checking Permissions Only in the UI

Hiding the "Delete" button for non-admins doesn't stop anyone from calling DELETE /api/projects/9 directly. Every server endpoint must check.

Trusting Client-Supplied Identity

Derive the acting user from the verified session or token, never from the request body, and verify that from_account belongs to them.

Forgetting Secondary Paths

Main endpoints are protected, but CSV exports, search APIs, GraphQL nested fields, webhooks, file URLs, and legacy v1 endpoints aren't. Inventory all access paths to sensitive data.

FAQ

What is an IDOR vulnerability?

Insecure Direct Object Reference: the application uses a user-supplied identifier (an ID in a URL, body, or header) to fetch an object without verifying that the user may access it. Changing the ID exposes or modifies other users' data. In API security terminology, it's called Broken Object Level Authorization (BOLA).

Why is broken access control the #1 OWASP risk?

It's extremely common, since every application has custom authorization logic and one missed check is enough. It's often easy to exploit (changing an ID), and its impact is severe: mass data exposure, account takeover, or privilege escalation. Automated scanners also struggle to find it, because they don't know the business rules.

Do UUIDs prevent IDOR?

They make blind enumeration impractical, but IDs still leak through URLs, logs, emails, shared links, and other API responses. Any leaked ID becomes an attack if authorization isn't checked. UUIDs are defense in depth, not a fix.

What's the best place to enforce multi-tenant isolation?

In several layers: tenant-scoped queries in the data access layer (ideally impossible to bypass accidentally), database row-level security as a safety net, and tests proving cross-tenant access fails. Also include the tenant in caches, search indexes, and file storage paths.

Related Topics

References