CWE-862: Missing Authorization
Overview
Missing Authorization occurs when a sensitive action or data read has no permission check at all. The caller's identity may be established correctly, but nothing on the path checks whether that identity is allowed to do what it is asking to do. The usual cause is an endpoint that confirms the caller is logged in and stops there: an admin-only action reachable by any authenticated user, a new route that never inherited the shared authorization decorator or middleware its siblings use, or a resource lookup that trusts an identifier from the request instead of confirming the caller has a relationship to that specific record.
Relationship to Other CWEs
CWE-862 sits under CWE-285 (Improper Authorization), which MITRE marks Discouraged for mapping - report the finding here rather than against the class. CWE-285's page is the one to climb to when it is unclear whether any authorization decision was reached at all.
- CWE-306 (Missing Authentication for Critical Function) - applies one step earlier, when no identity check exists at all.
- CWE-862 (this page) - identity is already known, and the permission check on top of that identity is missing.
- CWE-863 (Incorrect Authorization) - a permission check exists but its logic is wrong: a bad comparison, an inverted condition, a client-supplied role taken at face value. CWE-862 is the case where no check runs on the path at all.
- CWE-639 (Authorization Bypass Through User-Controlled Key) - the narrower IDOR case, where the gap is tied to a user-controlled object identifier rather than to the general absence of any role, permission, or ownership check. MITRE files it under CWE-863 rather than CWE-862, on the reading that the endpoint does run an authorization decision and does not include the object in it, so a finding that arrives as CWE-862 on an endpoint with an identifier in the request is usually better served by the CWE-639 page.
OWASP Classification
A01:2025 - Broken Access Control
Risk
Critical: A sensitive action with no authorization check is reachable by any authenticated user, whatever role they hold. Depending on what the action does, that ranges from exposing another user's data to letting a standard user delete records, change another account's settings, or grant themselves elevated access.
Remediation Steps
Core Principle: Add an explicit authorization check - role, permission, or resource ownership - on every sensitive action, enforced through the same centralized mechanism used by comparable protected paths, and deny by default when the check cannot be evaluated.
Trace the Data Path
- Source: An authenticated request, RPC call, or background job carrying an already-verified caller identity
- Sink: The sensitive action itself - a state change, a non-public data read, or an administrative capability
- Missing Control: No role, permission, or ownership check runs between identity verification and execution - the route was never registered under the shared authorization middleware or decorator, or the only check present confirms that the caller is logged in
Add Explicit Authorization Checks (Primary Defense)
- Enumerate every route, RPC method, resolver, and background job that performs a sensitive action or returns sensitive data, and compare that list against what the authorization layer covers; anything not covered is a candidate gap
- Add a role or permission check - does the caller hold the capability this action requires - through the same centralized middleware, decorator, or policy layer the sibling routes use, rather than an inline one-off check
- Apply the check by default so new routes inherit it, instead of relying on a developer to add it per route
Cover Resource-Level Ownership (Primary Defense)
- A role or permission check only confirms the caller can perform this type of action - for anything that operates on a specific record, also verify the caller owns it or has a granted relationship to it
- Load the resource server-side and compare its owner, tenant, or ACL entry to the authenticated caller's identity before returning or mutating it; never trust a role claim or an ownership flag supplied by the client
- Apply this to every operation on the resource (read, update, delete, export), not just the one that was reported - a check added to one route and skipped on a sibling leaves the same resource reachable through the other path
Fail Closed by Default (Defense in Depth)
- If the authorization decision cannot be evaluated - the permission is undefined, the check errors, the resource fails to load - deny the request rather than default to allow
- Run every check server-side on every request; client-side route guards and hidden UI controls decide nothing
- Set a deny-by-default fallback for any route that matches no specific rule, so an unattributed or newly added endpoint fails closed instead of falling through to broad access
Test Directly Against the Endpoint
- Call the route or handler as an authenticated-but-unprivileged user - not just through the UI - and confirm the response is a rejection, not a success or a UI-only restriction. Where the request names no particular record, 403 is the right answer: refusing it tells the caller nothing about what exists
- Log in as a different, unrelated user and request another user's resource by ID to confirm ownership is enforced, not just role. Then request an ID that does not exist at all, and confirm the two responses are identical in both status and body - a 403 for one and a 404 for the other is an existence oracle, and so is a matching status over two different error bodies
- Re-scan with the security scanner to confirm the finding is resolved
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
route "/orders/{id}/refund" -> requireAuthentication -> refundOrderHandler
// confirms the caller is logged in, but never checks role or that the
// caller owns this specific order
// Attack: any authenticated user calls /orders/12345/refund directly,
// including orders that belong to other accounts
// Result: the handler executes with no permission or ownership check
Why this is vulnerable: the route answers a different question from the one it appears to answer. requireAuthentication establishes who the caller is; nothing on the path establishes what that caller may do. The gate is present and positively named, so the route reads as protected, and the gap between "logged in" and "allowed" is crossed by changing an identifier in a URL.
This is a weakness of absence, which changes how it has to be found. Every line in the handler is correct, there is no dangerous call to grep for, and a diff of the file shows nothing - the defect is a check nobody wrote. It clusters around new routes and refactors, where a handler was added alongside others that already carried the middleware, so finding the rest of them means comparing an endpoint against its siblings rather than reading it on its own.
Secure Patterns
// SECURE - pseudo-code
route "/orders/{id}/refund" ->
requireAuthentication ->
requirePermission("orders:refund") ->
requireResourceOwnership(loadOrder, currentUser) ->
refundOrderHandler
// authorization applied through the same centralized mechanism every
// comparable route uses, covering both the action and the specific record
Why this works: Both checks hang off the shared middleware chain rather than the handler, so a new or overlooked route inherits them instead of depending on a developer adding them by hand. requireResourceOwnership compares the caller against a server-loaded copy of the order, so a caller who is authenticated and holds orders:refund still cannot refund an order that isn't theirs.
Common Pitfalls
- Checking role but not ownership: Confirming the caller holds the right role or permission for this type of action, then performing it on whatever resource ID the request supplies without checking that the specific record belongs to the caller - a valid role does not imply ownership of every record of that type.
- Enforcing authorization in the UI only: Hiding admin controls or restricted buttons from users without the right role, while the underlying endpoint performs no server-side check - an attacker who calls the endpoint directly bypasses the restriction entirely.
- Scoping the list endpoint but not the detail endpoint: Filtering an index view to the caller's own records while the single-record detail, update, or delete endpoint for the same resource still operates on the ID alone - the correctly scoped list creates false confidence that the resource is protected.
- Adding the check to only one HTTP method: Protecting the
GETfor a resource while thePUT,PATCH, orDELETEfor the same resource skips the check, on the assumption that reaching the write endpoint implies the read endpoint's check already ran - each operation needs its own independent check.
Language-Specific Guidance
- C#/.NET - ASP.NET Core policy-based authorization and resource-based authorization handlers
- Go - middleware-based role checks and manual resource-ownership verification
- Java - Spring Security
@PreAuthorizemethod security and Jakarta EE@RolesAllowed - JavaScript/Node.js - Express middleware guards and NestJS
@UseGuards - PHP - Laravel Policies/Gates and Symfony Voters
- Python - Django permission classes/decorators and Django REST Framework object-level permissions