Broken access control happens when an application lets an authenticated or anonymous caller read, change or invoke something it was never meant to reach, usually because the server trusted an identifier, a role or a route instead of checking it. OWASP reports that every application in its 2025 testing data had some form of broken access control, and no scanner reliably finds it because only the application knows who owns a record. This guide to preventing broken access control explains insecure direct object references (IDOR), forceful browsing, HTTP verb tampering and privilege escalation, then covers server-side enforcement, testing, detection and rollout. It is for application security engineers, developers and testers, and contains no exploit requests or attack procedures. For the wider picture see the Web Application Attacks Guide.
Key takeaways
- Authorisation is a per-request, per-object decision. Logging in proves who the caller is, not whether they may touch this record, field or function.
- Deny by default, on the server, in one place. OWASP, ASVS and CWE converge: access control only works in trusted server-side code, built once and reused.
- Unguessable identifiers are not a control. UUIDs and random tokens reduce enumeration, but OWASP is explicit that an ownership check is still required on every object access.
- Test with two accounts and an authorisation matrix. Scanners find little here; the reliable test is replaying one user's object references and functions from another user and role, automated in CI.
- Delegated callers inherit the problem. AI agents, background jobs and services acting for a user must be checked against the originating user's permissions, not their own broad service credentials.
What broken access control is, and how it is classified
Access control rests on three mechanisms that CWE-284 names: authentication (who is calling), authorisation (what they may do) and accountability (a record of what they did). Every broken access control vulnerability is a failure of the second. CWE-285 Improper Authorization describes a product that "does not perform or incorrectly performs an authorization check". Most findings fall into one of two shapes: a check that is missing altogether (CWE-862) or a check that exists but asks the wrong question (CWE-863), for example confirming the caller is logged in rather than that they own the record. Either way the result is an authorisation bypass.
| Identifier | Name | Level | Use it for |
|---|---|---|---|
| CWE-284 | Improper Access Control | Pillar | Classification only; CWE discourages mapping real vulnerabilities to it |
| CWE-285 | Improper Authorization | Class | Grouping; CWE discourages mapping and points to 862, 863 or 732 |
| CWE-862 | Missing Authorization | Class | Endpoints or functions with no check at all (forceful browsing, missing function-level checks) |
| CWE-863 | Incorrect Authorization | Class | Checks that exist but test the wrong condition |
| CWE-639 | Authorization Bypass Through User-Controlled Key | Base | IDOR: the record key comes from the caller and ownership is not verified |
| CWE-732 | Incorrect Permission Assignment for Critical Resource | Class | Files, buckets or objects with permissions wider than intended |
Map findings to the most specific entry (usually CWE-639, 862 or 863); CWE notes that CWE-284 is often used in low-information reports and is not useful for trend analysis.
OWASP A01 Broken Access Control in 2021 and 2025
Broken Access Control is A01 in both recent editions of the OWASP Top 10. A01:2021 moved up from fifth place in 2017 and mapped 34 CWEs. A01:2025 maps 40 CWEs, records 1,839,701 occurrences and 32,654 related CVEs, and its notable CWEs now include CWE-918 server-side request forgery, which was the separate A10 category in 2021. The 2025 category also lists CSRF (CWE-352) and information exposure (CWE-200, CWE-201). See the OWASP Top 10 Guide for the full list.
The access control bypass patterns
Every access control bypass below is the same mistake in a different place: the server accepted something the caller controls as proof of entitlement.
| Pattern | How it works | Usual root cause | Control that removes it |
|---|---|---|---|
| IDOR (insecure direct object reference) | The caller changes a record, file or account identifier and receives or modifies someone else's object | Lookup by key with no ownership or tenant check (CWE-639) | Ownership check or owner-scoped query on every object access |
| Forceful browsing (CAPEC-87) | The caller requests pages, files or API routes that are not linked for them, unauthenticated or from a lower role | Protection relies on links being hidden (CWE-862) | Deny by default at the routing layer; every route declares its required permission |
| Missing function-level access control | A normal user invokes an administrative or privileged operation directly | Role checks only in the user interface or only on some methods | Function-level permission checks in the service layer (ASVS 8.2.1) |
| Parameter tampering and URL tampering | The caller edits query strings, hidden fields or path segments carrying role, price, account or status | Server trusts client-supplied state | Derive identity, role and tenant from the server-side session; ignore client copies |
| HTTP verb tampering | A different or unrecognised method, or a method-override header, reaches a handler that the protected method guarded | Rules bound to specific methods; framework treats unknown verbs as GET | Allow only required methods per route; apply the same check to every method; disable override headers |
| Metadata and token manipulation | Edited cookies, JWT claims or hidden fields change role or identity | Unsigned or unverified client state | Server-held sessions or verified, short-lived signed tokens; no authorisation data the client can edit |
| Front-end path restriction bypass | Headers that rewrite the target URL or claim an internal client IP get past a proxy rule | Authorisation done by URL pattern at the edge only | Enforce in the application; strip untrusted forwarding and rewrite headers at the edge |
| Cross-tenant access | A caller in one tenant reaches another tenant's records | Tenant ID taken from the request rather than the session | Tenant scoping in every query (ASVS 8.4.1), ideally enforced by the data layer |
WSTG-CONF-06 notes that some servers treat unrecognised methods like GET and that HEAD may run the GET code path, so a rule guarding only GET or POST is not a control.
IDOR in depth
An IDOR vulnerability is what CWE-639 calls authorization bypass through a user-controlled key (OWASP's term is broader). The OWASP IDOR Prevention Cheat Sheet says it "occurs due to missing access control checks" and that "Object references are not limited to numeric identifiers": filenames, account numbers, invoice references, export job IDs and tokens all count. WSTG-ATHZ-04 adds that a reference can be split across several parameters, so a check on one field can be bypassed through another. API teams call it broken object level authorisation; see the API Security Guide.
- Horizontal IDOR: two users with the same privilege reach each other's data. CWE-639 notes the attacker usually needs an authenticated account, so login logs see nothing unusual.
- Blind IDOR: the response does not reveal the other user's data, but the action still happens: an update, delete, password change, share or export is applied to someone else's object while the caller sees a generic success message. Test by checking the victim object, not the response.
- Write and action IDOR: WSTG-ATHZ-04 calls out operations where an identifier names the target of an action, such as whose password is changed, without re-verifying the requester.
- Indirect references: random identifiers help as defence in depth. OWASP says "even with complex identifiers, access control checks are essential" and advises against encrypting identifiers.
- Static and stored resources: the OWASP Authorization Cheat Sheet says to "Ensure that static resources are incorporated into access control policies". Predictable or long-lived storage links are IDORs too.
AI agent IDOR
AI agent IDOR is a confused-deputy problem. An agent takes object identifiers from prompts, model output or retrieved content, then calls a tool with a service credential that reaches every customer's records; if the tool checks only that credential, the agent fetches or modifies whatever it was steered towards. ASVS 5.0 requirement 8.3.3 states the fix: object access is based on the originating consumer's permissions, not those of an intermediary or service acting on their behalf. Pass the end user's identity through (token exchange or on-behalf-of flows), run the ownership check in the tool or API, and never let the model decide who owns a record. See the AI Agent Authorisation Guide.
Horizontal and vertical privilege escalation
The OWASP Authorization Cheat Sheet says "Least Privileges must be applied both horizontally and vertically", and notes that horizontal escalation is especially common.
- Horizontal privilege escalation: acting on peers' resources at the same privilege level. The control is object-level authorisation: ownership, tenant and relationship checks.
- Vertical privilege escalation: gaining functions of a higher role, such as a customer reaching staff refunds or a user reaching tenant administration. The control is function level access control: every privileged operation declares its required permission and the service layer refuses everyone else. OWASP's 2013 Top 10 called the gap "missing function level access control" (A7).
- Context-dependent escalation: skipping workflow steps, approving one's own request or acting on an object in a state that should be locked. OWASP A01 asks teams to enforce business limits in domain models, and ASVS 8.1.2 expects documented rules that depend on object state.
- Field-level escalation: a permitted update that also writes a role, owner or price field. ASVS 8.2.3 requires field-level permissions; bind only allowlisted fields on input.
Server-side authorisation checks that hold
OWASP A01:2025 says access control is only effective in trusted server-side code or serverless APIs, where the caller cannot modify the check or its metadata, and ASVS 8.3.1 requires enforcement in a trusted service layer rather than client-side JavaScript.
- Deny by default: the router or framework refuses any route, method or resource without an explicit rule. An unannotated endpoint fails closed.
- One enforcement point: OWASP asks teams to implement access control once and reuse it. Use middleware or a shared service, not ad hoc checks per controller; the cheat sheet also says to "Centralize the logic for handling failed access control checks" so errors are consistent and leak nothing (CWE-209).
- Identity from the session, never the request: user ID, tenant ID and roles come from the server-side session or a verified token, not from body fields, query strings or headers.
- Object ownership checks: load the object, then decide. A safe pattern in pseudocode:
Owner-scoped queries mean a forgotten check returns nothing rather than another customer's data.record = orders.find(id = requested_id, tenant = session.tenant_id) if record is missing: return not_found if not policy.allows(session.user, "refund", record): return not_found_or_forbidden proceed with the action - Prefer ABAC or ReBAC for object decisions: the cheat sheet says "ABAC and ReBAC should typically be preferred for application development", because roles alone cannot express "own records only". See the Authorisation Models Guide.
- Short-lived authority: invalidate server-side sessions at logout and keep stateless tokens short-lived. ASVS 8.3.2 (Level 3) expects permission changes to take effect immediately, or mitigating controls where self-contained tokens make that impossible.
Where to enforce: choosing the layer
| Layer | Catches | Misses | Best for |
|---|---|---|---|
| API gateway or edge proxy | Unauthenticated calls, coarse route and scope rules, rate limits | Ownership, tenant and field rules; anything needing the object | First filter, never the only control |
| Route or controller middleware | Function-level permissions, method restrictions, deny-by-default routing | Object ownership unless the object is loaded | Vertical escalation and forceful browsing |
| Service or domain layer | Ownership, workflow state, business limits, field-level rules | Paths that bypass the service layer (batch jobs, direct queries) | Most object-level decisions |
| Data layer (scoped repositories, row-level security) | Cross-tenant reads even when a check is forgotten | Complex relationship and action rules | Multi-tenant SaaS as a backstop |
| External policy engine (decision point called by each service) | Consistent rules across many services and languages, auditable policy | Nothing on its own; each service must still call it with the right object attributes | Large estates with many teams |
Decision guidance
- In-code checks or a policy engine: one application and one team are usually safer with framework-native declarative checks. Choose an external policy engine when many services share rules or auditors need policy evidence; budget for latency and stale caches.
- RBAC or attributes and relationships: roles for functions, attributes or relationships for ownership, sharing and tenancy. Most applications need both.
- Random identifiers: adopt them for new public-facing resources to slow enumeration, but do not schedule them as the IDOR fix.
- Small versus large organisations: small teams pick deny-by-default framework routing and automate two-account tests for the 20 most sensitive endpoints; large estates publish a shared authorisation library, make a declared permission a build rule and run cross-tenant regression centrally.
Testing for broken access control
CWE-285 rates architecture and design review as highly effective, static analysis as limited for custom schemes and dynamic scanning as able to find unprotected interfaces but not to judge business rules.
| Method | Finds well | Finds poorly | Reference |
|---|---|---|---|
| Authorisation matrix review | Missing rules, over-broad roles, undocumented admin functions | Implementation drift | ASVS 8.1.1, 8.1.2 |
| Unit and integration tests per rule | Regressions, deny-by-default gaps, field-level writes | Rules nobody wrote down | OWASP A01 prevention list |
| Two-account and multi-role replay testing | Horizontal and vertical escalation, blind IDOR if victim state is checked | Endpoints the tester never discovers | WSTG-ATHZ-02, WSTG-ATHZ-04 |
| HTTP method testing | Verb tampering, override headers, HEAD side effects | Object-level rules | WSTG-CONF-06 |
| Static analysis | Routes without annotations, input flowing to lookups without a check | Whether the check asks the right question | CWE-639 detection notes |
| Dynamic scanning | Forceful browsing to unlinked paths, unauthenticated access | Ownership and business rules | CWE-285 detection notes |
- Build the matrix first: endpoints and actions (read, create, update, delete, export, share, admin) against roles and relationships (owner, peer, other tenant, anonymous, admin), each cell with an expected result.
- Automate the replay: create two users per role in two tenants and replay every request as every other identity in CI. OWASP asks testers to verify denial "regardless of whether the object identifier is predictable".
- Include the edges: WSTG-ATHZ-02 adds access after logout, URL-rewrite and client-IP headers trusted by front-end rules, and non-standard ports; see the Penetration Testing Guide.
Detection and measurement
OWASP A01 asks teams to log access control failures and alert on repeated failures, which needs structured decision logs in "consistent, well-defined formats that can be readily parsed for analysis" (Authorization Cheat Sheet).
- Log every decision point: subject (and the user an agent or service acts for), tenant, action, resource type and ID, resource owner, decision, rule ID and correlation ID; never object contents.
- Enumeration: one token touching many distinct object IDs owned by many accounts in a short window.
- Probing: bursts of 403 or 404 responses on object routes from one identity.
- Vertical attempts: any non-admin identity calling an admin route, and any unusual HTTP method or override header on sensitive routes.
- Cross-tenant signals: a decision where subject tenant and resource tenant differ is rare enough to alert on every time.
- Agents and services: alert when a delegated call has no originating user attached.
Metrics that show the programme is working:
- Percentage of endpoints with a declared permission and an owner-scoped data path (target 100% for internet-facing routes).
- Percentage of authorisation matrix cells covered by automated tests, and number of builds blocked by failing authorisation tests.
- Access control findings per release from testing and bug bounty, split by CWE-639, 862 and 863, and mean time to fix.
- Cross-tenant denials per million requests, with every one triaged.
- Share of delegated (agent and service) calls that carry the originating user's identity.
Implementation roadmap
- Inventory and rank: rank endpoints by data sensitivity and exposure; start with exports, account changes, payments and admin functions.
- Write the authorisation matrix with product owners; it is the test oracle and the ASVS 8.1 documentation.
- Turn on deny-by-default routing in audit mode: log, do not block, requests reaching routes without a declared rule, and fix each.
- Add ownership checks and scoped queries through the shared library, behind a per-route feature flag so a faulty rule rolls back without a redeploy.
- Switch to enforce route by route once audit logs show no legitimate denials across a full business cycle, month-end jobs included.
- Automate the two-account suite in CI and make failures block release.
- Extend to delegated callers: agents, jobs and services carry the originating user.
- Wire detection to the decision logs and review metrics monthly.
Framework mapping
| Framework | Where broken access control prevention sits |
|---|---|
| OWASP Top 10 | A01:2025 Broken Access Control; A01:2021 Broken Access Control |
| OWASP ASVS 5.0 | V8 Authorization: 8.1.1, 8.1.2 documentation; 8.2.1 function level; 8.2.2 data level (IDOR); 8.2.3 field level; 8.3.1 server-side enforcement; 8.3.3 originating consumer; 8.4.1 cross-tenant |
| OWASP WSTG | WSTG-ATHZ-02 bypassing authorisation schema; WSTG-ATHZ-04 IDOR; WSTG-CONF-06 HTTP methods |
| CWE | CWE-639, CWE-862, CWE-863, CWE-732 (map to these); CWE-284 and CWE-285 for grouping only |
| NIST SP 800-53 Rev. 5 | AC-3 Access Enforcement; AC-6 Least Privilege, AC-6(10) prohibit non-privileged users executing privileged functions; AU-2 event logging; SA-11 developer testing |
| NIST CSF 2.0 | PR.AA-05 access permissions managed and enforced with least privilege |
| CIS Controls v8.1 | 6.8 role-based access control; 16.10 secure design principles; 16.12 code-level security checks; 16.13 application penetration testing |
| ISO/IEC 27001:2022 Annex A | A.5.15 Access control; A.8.3 Information access restriction; A.8.26 Application security requirements; A.8.28 Secure coding; A.8.29 Security testing in development and acceptance |
| MITRE ATT&CK | T1190 Exploit Public-Facing Application when an access control flaw is used for initial access |
Common failure patterns
- Authorisation in the user interface only: buttons and menus are hidden, but the API behind them accepts any authenticated caller. OWASP's 2025 example of calling an admin endpoint directly is this mistake.
- Checking authentication instead of ownership: the handler confirms a valid session and then loads whatever ID it was given. The McHire flaw paired this horizontal IDOR with a default password and exposed millions of job applications.
- Treating UUIDs as the fix: identifiers leak through logs, links and referrers, and the missing check stays missing.
- Read paths fixed, write paths forgotten: teams test viewing records but not update, delete, share and export, so blind IDOR survives.
- Method-specific rules: a rule guards one HTTP method while the framework also routes HEAD, unknown verbs or override headers to the same handler.
- Service credentials doing user work: jobs, integrations and AI agents run with a broad service account and no originating user, so one manipulated request reaches every tenant. See the NHI Ownership Guide.
Practitioner checklist
- Configure routing to deny by default and fail the build for any endpoint without a declared permission.
- Take user, tenant and role only from the server-side session or a verified token, never from request fields.
- Apply an ownership or relationship check, or an owner-scoped query, on every object read, write, share and export.
- Enforce function-level permissions in the service layer for every privileged operation, whatever the HTTP method.
- Restrict each route to the methods it needs and disable method-override headers.
- Bind only allowlisted fields on input so updates cannot change owner, role or price.
- Require delegated callers, including AI agents, to carry and be checked against the originating user's permissions.
- Maintain an authorisation matrix and run automated two-account, two-tenant tests in CI, checking victim state for write actions.
- Log every authorisation decision with subject, resource, owner, decision and rule, and alert on enumeration and cross-tenant denials.
- Map findings to CWE-639, 862, 863 or 732 and track them per release.
Standards and references
- OWASP: A01:2025 Broken Access Control
- OWASP: A01:2021 Broken Access Control
- OWASP: Authorization Cheat Sheet
- OWASP: Insecure Direct Object Reference Prevention Cheat Sheet
- OWASP: Application Security Verification Standard 5.0, V8 Authorization
- MITRE: CWE-639 Authorization Bypass Through User-Controlled Key
- MITRE: CWE-285 Improper Authorization
- MITRE: CWE-284 Improper Access Control
Related NHI Mgmt Group resources: Web Application Attacks Guide · OWASP Top 10 Guide · API Security Guide · Authorisation Models Guide · AI Agent Authorisation Guide