Cybersecurity

Broken Access Control Explained: Why It Is the Number One OWASP Risk

Understand broken access control, how authorization failures expose data and functions, and how to design server-side controls that fail safely.

CybersecurityOWASPAccess ControlApplication SecurityAuthorization
Broken Access Control Explained: Why It Is the Number One OWASP Risk article cover

A user can log in successfully and still be able to do something they should never be allowed to do. That distinction is the heart of broken access control.

Authentication establishes an identity. Authorization decides what that identity may read, create, change, delete or execute. When an application makes the wrong authorization decision, or forgets to make one at all, a normal account can become a path to another user’s data, an administrative function or a resource outside the user’s intended scope.

Broken Access Control remains A01 in the OWASP Top 10:2025. OWASP’s contributed data for the 2025 release mapped 40 CWEs to this category and found an average incidence rate of about 3.73 percent across tested applications. That number is useful context, but the more important lesson is architectural: almost every non-trivial application has authorization rules, and those rules must remain correct across pages, APIs, background actions, files and business workflows.

Authentication and authorization are different decisions

Suppose an invoicing application requires a login. Customer A requests:

GET /api/invoices/4821

The server confirms that the session belongs to a valid user and returns invoice 4821. Customer A then changes the identifier:

GET /api/invoices/4822

If invoice 4822 belongs to Customer B, the server must ask a second question: is this authenticated identity allowed to read this specific invoice? If it checks only that the requester is logged in, authentication worked while authorization failed.

This is why hiding a button, disabling a form control or keeping an API URL out of the interface is not access control. A client is under the user’s control. Requests can be changed independently of what the browser interface displays.

MITRE’s CWE-284 describes improper access control as failing to restrict, or incorrectly restricting, access to a resource from an unauthorized actor. It separates the problem into specification failures, where privileges or permissions are wrong, and enforcement failures, where the intended policy exists but the product does not correctly enforce it.

What broken access control looks like

The weakness is broader than changing a numeric ID. It can appear at several layers of an application.

Object-level authorization failures happen when a user can access another user’s record by changing an identifier. In API security this pattern is often discussed as broken object level authorization, while the web application community has long used terms such as insecure direct object reference.

Function-level failures expose operations that should belong to a different role. A standard account might call an administrative endpoint directly even though the interface never displays the admin control.

Field-level failures occur when a user may access a record but can read or change fields that should remain restricted. For example, an employee may be allowed to update a profile but not a field that controls account privileges.

Tenant-boundary failures are especially important in multi-tenant systems. A valid user in Organisation A must not gain access to Organisation B simply because both organisations use the same application and database.

Access control also applies to files, cloud objects, server routes, API methods and business actions. OWASP’s 2025 category now also incorporates Server-Side Request Forgery (SSRF), reflecting a broader view of controls over which resources and destinations application components are permitted to access.

A safer authorization decision path

Server-side authorization decision flow showing authentication, policy and resource-scope checks.

Original authorization decision flow for this article. The exact policy model varies by application, but protected requests should reach resources only after trusted server-side authorization succeeds.

The diagram deliberately places the decision on the server. The application should not trust a role, ownership claim or permission simply because the browser sends it.

A useful mental model for a protected request is:

  1. Establish the authenticated identity from a trusted session or token validation process.
  2. Identify the requested action and resource.
  3. Evaluate the application’s authorization policy for that identity, action and resource.
  4. Verify contextual constraints such as ownership, tenant, state or business limits where required.
  5. Deny the request unless the policy explicitly permits it.
  6. Record meaningful authorization failures so repeated or suspicious attempts can be investigated.

This is more reliable than scattering unrelated if statements across controllers and hoping every new endpoint receives the right check.

Why client-side checks fail

Consider an administrative button that is rendered only when JavaScript sees role = admin. That can improve the user experience by hiding controls that a normal user cannot use. It does not protect the server endpoint behind the button.

A user can construct the HTTP request without clicking anything. If the server accepts the request because it assumes the interface already performed the check, the security boundary is missing.

The same issue appears when applications trust hidden form fields, editable identifiers, client-provided prices or role values. Client-side validation can provide useful feedback, but a security decision must be repeated in a trusted execution environment.

OWASP’s A01:2025 guidance states that access control is effective when implemented in trusted server-side code or serverless APIs where an attacker cannot modify the check or its metadata. It recommends denying by default and reusing consistent access-control mechanisms throughout the application.

Ownership is not the same as knowing an identifier

An identifier answers “which object?” It does not answer “who owns it?”

A common design mistake is to query a record by its ID and then return it because the query succeeded. A safer data access pattern incorporates the authorization scope into the lookup itself. Conceptually:

requested invoice = find invoice
                    where invoice.id = requested_id
                    and invoice.customer_id = authenticated_customer_id

The exact implementation depends on the framework and policy model. The important property is that the application derives the customer identity from trusted authentication context rather than accepting customer_id from the requester as proof of ownership.

Random or hard-to-guess identifiers can reduce casual enumeration, but they are not a substitute for authorization. If a leaked identifier grants access by itself, the underlying control is still broken.

Role-based access control helps, but roles are not enough

Role-based access control (RBAC) is useful when permissions map naturally to stable responsibilities such as student, instructor, editor or administrator. A central policy might say that editors can publish articles while ordinary users cannot.

Real applications often need more context. An instructor may edit only courses assigned to that instructor. A finance employee may approve transactions only below a defined threshold. A support agent may view a customer record but not change security settings.

This is why authorization may combine roles with attributes, relationships, ownership and business state. The goal is not to choose the most complicated model. It is to express the application’s actual rules clearly enough that they can be enforced and tested consistently.

Deny by default and apply least privilege

A deny-by-default policy changes the failure mode. Instead of assuming a request is allowed unless someone remembered to block it, the application requires an explicit reason to permit protected access.

Least privilege complements that approach. Accounts, service identities and application components should receive only the permissions needed for their purpose. Excessive privilege increases the damage possible when an account is compromised or a control is bypassed.

OWASP’s Developer Guide recommends designing authorization up front, forcing protected requests through access-control checks, denying by default, applying least privilege, logging authorization events and creating unit and integration tests that verify business rules and authorization criteria.

Authorization should be tested as a matrix

Happy-path testing asks whether an authorised user can complete a legitimate action. Security testing must also ask whether the wrong user can complete it.

An access-control matrix makes those expectations explicit:

Actor Resource Read Update Delete
Record owner Own profile Allow Allow selected fields Deny
Other user Someone else’s profile Deny Deny Deny
Support role Customer profile Allow required fields Limited Deny
Administrator Managed account Allow Allow by policy Allow by policy

This table is only an example, not a universal policy. Its value is that each cell becomes a testable expectation. Teams can turn important deny cases into automated tests so a future route or refactor does not silently remove a control.

Testing should cover different HTTP methods and alternate application paths as well. A GET route may correctly restrict access while a PUT, DELETE, export endpoint or mobile API exposes the same object without an equivalent check.

Logging matters after the decision

A denied request is often the correct outcome, but repeated denials can also reveal attempted abuse. Useful security logging can record enough context to investigate authorization failures without placing secrets or unnecessary sensitive data into logs.

OWASP recommends logging access-control failures and alerting administrators when appropriate, such as repeated failures. Rate limiting can also reduce the impact of automated attempts against APIs and controllers.

Logging is not a replacement for prevention. It is the accountability layer that helps a team see whether controls are being tested, misused or attacked.

Common mistakes to avoid

Several shortcuts repeatedly weaken authorization:

  • checking permissions only in the user interface;
  • trusting a user ID, tenant ID or role supplied by the client;
  • protecting page routes but forgetting API or alternate HTTP methods;
  • assuming an unguessable object identifier is an authorization control;
  • giving new roles broad permissions because the policy is unclear;
  • duplicating authorization logic in many places until implementations drift;
  • testing only successful access and not expected denials;
  • allowing a failed or exceptional authorization check to fall through as permitted access.

Centralisation does not mean every decision must be identical. It means the application should have a coherent mechanism for expressing and enforcing its different rules.

A practical review checklist

When reviewing an application, start with the resources and actions that would matter most if accessed incorrectly: personal data, payments, administrative functions, account settings, exports, files and cross-tenant resources.

For each one, ask who should be allowed to perform each action and what evidence the server uses to make that decision. Trace every route to the resource, including APIs and background operations. Verify deny cases with automated tests where practical. Review permissions when roles or business processes change.

For a broader application-security view, see OWASP Top 10:2025 Explained with Practical Examples. The related Cybersecurity vs Information Security guide explains where application controls fit within wider information protection, while What Is AI Security? discusses how authorization and tool permissions also matter when AI systems can take actions.

The main lesson

Broken access control is not primarily a problem of clever URLs. It is a failure to make or enforce the correct permission decision at a trust boundary.

A strong design makes authorization explicit, server-side, deny-by-default and testable. It ties access to the authenticated identity and the actual resource or action, rather than to what the interface happens to show. That approach does not eliminate every authorization bug, but it gives developers a policy they can reason about and security teams a set of decisions they can verify.

Sources and further reading