OWASP, the Open Web Application Security Project, publishes a periodically updated list of the most critical web application security risks. In the 2021 edition, broken access control moved into the top position, displacing injection vulnerabilities that had held that rank for years.
This wasn't because access control suddenly became more vulnerable. It was because broken access control became more prevalent and more consequential as web applications grew more complex, more interconnected, and more capable of handling sensitive data at scale.
Understanding why broken access control dominates the landscape is the first step to building systems that don't contribute to it.
What Broken Access Control Actually Means
Access control is the set of policies that determines which users can access which resources and perform which actions. Broken access control means those policies aren't working correctly: users can access resources they shouldn't, perform actions they shouldn't, or escalate to privileges they weren't granted.
According to Wikipedia's overview of access control in computer security, role-based access control is the dominant model in enterprise software for a reason: it provides a principled way to define and enforce these policies. When it's implemented correctly, breaking it requires breaking the model. When it's implemented incorrectly, or not implemented at all, the attack surface is everything the application can do.
The OWASP categorization of broken access control includes several distinct vulnerability types:
Insecure direct object reference (IDOR): A user changes a record ID in a URL or request parameter and retrieves data belonging to another user. The check "is this user authenticated?" passed. The check "does this user own record 47291?" was never performed.
Missing function-level authorization: An admin endpoint exists and works correctly. It's just not protected against non-admin users who discover it by inspecting network traffic or guessing the URL pattern.
Horizontal privilege escalation: A user accesses another user's account, data, or resources at the same privilege level.
Vertical privilege escalation: A user acquires privileges above their assigned role, either by modifying their own role claim or by exploiting a gap in privilege verification logic.
All of these share a common cause: the authorization system was either not designed, not enforced consistently, or enforced at the wrong layer.
Why Access Control Is Hard to Get Right
The difficulty isn't conceptual. The concept is simple: each user has a role, each role has permissions, each action requires a permission. The difficulty is operational: applying this concept consistently across every endpoint, every query, and every data access path in a growing application.
Most access control failures are failures of consistency rather than failures of design. The permission model may be well-designed on paper. The implementation may be correct at 95% of access points. The problem is the 5% where a developer forgot to add the check, or added it in the wrong place, or assumed another layer was handling it.
The OWASP Access Control Cheat Sheet documents the most common implementation gaps. One of the most consistently cited is the difference between UI-level access control (hiding buttons or menu items from users who don't have permission) and server-side access control (enforcing permissions at the API layer). UI-level controls are not security controls. They're user experience elements. Any user who can make HTTP requests directly, which is any user, can bypass UI-level controls.
The Architectural Failure
The deeper issue is that broken access control often results from architectural decisions made early in development that are hard to reverse later.
Applications that grew from small internal tools to production systems often have authorization logic that was added incrementally: a check here, a conditional there, a is_admin flag that became is_admin || is_moderator || user.id === resource.owner_id. This kind of authorization is difficult to reason about, difficult to test, and difficult to audit.
CASL, the attribute-based authorization library for JavaScript, was built in part to solve this problem: authorization logic defined in one place, applied consistently everywhere, testable as a standalone module. The alternative, conditional logic scattered across hundreds of functions, is how broken access control accumulates.
Casbin addresses the same problem with a different approach: externalized policy files that define authorization rules independently of application code. This makes it possible to audit the authorization model without reading code, which is a significant advantage for compliance-focused teams.
What the Research Shows About Breach Causes
Access control failures are implicated in a large proportion of notable data breaches. The pattern is consistent: an API endpoint that was assumed to be internal is discovered by a user who changes an ID in a request. A role check that was supposed to gate an admin function was applied to the frontend but not the backend API. A migration that added a new resource type didn't add the corresponding authorization checks.
JWT.io documentation emphasizes a common access control failure specific to token-based authentication: accepting role or permission claims from a JWT without verifying the token's signature. Tokens that can be modified by users without detection allow any user to claim any role. This is a vertical escalation vulnerability that's entirely preventable with correct token verification.
Open Policy Agent represents the direction the industry is moving for complex authorization: centralized policy definition and evaluation, decoupled from application code, auditable as configuration. For applications where authorization complexity has outgrown what can reasonably be managed in application code, OPA provides a principled path forward.
The Development Process Problem
Broken access control isn't just a security problem. It's a process problem. Security testing in most organizations is concentrated near the end of a development cycle, when finding a structural authorization failure is expensive to fix. Authorization design, by contrast, should happen at the architecture stage, when changing the model is cheap.
The development practices that prevent broken access control are:
- Defining the permission model as a formal document before writing code
- Building authorization as a centralized service or module that all code depends on, rather than as scattered conditional checks
- Testing authorization properties explicitly: for each resource type, verify that each role can perform the actions it should and cannot perform the actions it shouldn't
- Reviewing authorization model changes as a separate step in code review
- Running regular authorization audits that check whether the implemented permissions match the intended model
The technical guide How to Implement Role-Based Access Control in Web Applications covers the implementation approach that addresses these process gaps.
Moving Forward
Broken access control being the top OWASP risk isn't a verdict on developer quality. It's a signal that the tools, patterns, and processes most developers use aren't set up to prevent authorization failures by default.
The path forward involves treating authorization as an architectural concern rather than an implementation detail: designing the permission model before writing code, centralizing the authorization logic, enforcing it at both the route and data layer, and testing it explicitly as a system-level property.
137Foundry web development services includes this kind of authorization architecture work for web applications. Building authorization correctly from the beginning is meaningfully cheaper than retrofitting it after a breach makes it urgent.

Photo by Thijs van der Weide on Pexels

Top comments (0)