1

Vulnerability

Missing Authorization Checks

⚠️

Vulnerability

The most direct authorization failure is simply not performing an authorization check at all. The endpoint verifies that the requester is authenticated, then immediately performs the requested action without asking whether that authenticated identity is actually permitted to do it.

For example:

DELETE /api/posts/551

→ Is there a valid session?
   Yes ✓

→ Is this user allowed to delete post 551?
   Never checked ✗

→ Delete post

The server has successfully established who the requester is, but never establishes what that requester is allowed to do.

A vulnerable implementation might look like:

$post = Post::findOrFail($request->id);

$post->delete();

The fact that findOrFail() successfully found the post says nothing about whether the current user is authorized to delete it.

A secure implementation needs an explicit authorization decision:

Authenticated user
        ↓
Requested action: delete
        ↓
Requested object: post 551
        ↓
Does this user have permission?
        ↓
Allow / Deny

Depending on the application's rules, that might mean checking ownership:

if ($post->user_id !== auth()->id()) {
    abort(403);
}

or checking a role:

if (!auth()->user()->can('delete', $post)) {
    abort(403);
}

This vulnerability can therefore produce both horizontal and vertical authorization failures:

Normal user
→ deletes another user's post
→ horizontal failure

Normal user
→ deletes an admin-owned/protected post
→ potentially vertical/object-level failure

A valid session proves identity; it does not grant unrestricted access. Every protected operation needs an explicit authorization decision.

2

Vulnerability

Client-Side Authorization

⚠️

Vulnerability

A client-side authorization failure occurs when the application restricts access only in the interface, while the underlying server endpoint does not enforce the same permission.

For example, the UI might hide an administrative action from ordinary users:

Admin user
→ "Delete User" button visible

Standard user
→ "Delete User" button hidden

But the underlying endpoint may still be directly accessible:

POST /admin/delete-user
        ↓
Called directly by a standard user
        ↓
Server checks authentication ✓
Server checks admin permission ✗
        ↓
Action succeeds

The attacker doesn't need to make the hidden button appear. They can call the endpoint directly using a browser's developer tools, an API client, or another HTTP client.

The fundamental distinction is:

UI restriction
→ controls what the user can see or click

Server-side authorization
→ controls what the user is actually allowed to do

A hidden button therefore provides no security boundary:

"Delete User" button hidden
        ↓
Stops accidental interaction ✓
        ↓
Does not stop direct requests ✗

The server must independently evaluate the authenticated user's permission whenever the sensitive operation is requested:

POST /admin/delete-user
        ↓
Authenticate requester
        ↓
Authorize requested action
        ↓
Is this identity permitted to delete users?
        ↓
Allow / Deny

This applies equally to JavaScript checks, hidden HTML elements, disabled buttons, client-side routing, and other interface-level restrictions. An attacker controls the client and can modify or bypass all of them.

3

Vulnerability

Inconsistent Authorization

⚠️

Vulnerability

Inconsistent authorization occurs when the same resource or operation is accessible through multiple endpoints, but the authorization rules are not enforced consistently across all of them.

For example:

GET /orders/4522
→ ownership checked → blocked for non-owner

GET /api/v1/orders/4522
→ ownership checked → blocked

GET /api/v2/orders/4522
→ ownership check missing → succeeds

The application may therefore appear secure when tested through the main interface:

Main endpoint
→ authorization ✓

while a different entry point exposes the exact same resource:

New API endpoint
→ authorization ✗
→ same underlying data

This commonly appears after:

  • API versioning

  • application refactors

  • adding mobile or SPA-specific APIs

  • creating internal/admin endpoints

  • migrating from one controller or service to another

  • maintaining legacy routes alongside newer ones

The underlying mistake is usually duplicated authorization logic:

Web route
→ ownership check ✓

API v1
→ ownership check ✓

API v2
→ developer forgot to add it ✗

This is why authorization should ideally be centralized and consistently enforced, rather than manually reimplemented in every route.

4

Vulnerability

Hidden Functionality

⚠️

Vulnerability

Hidden functionality refers to pages or endpoints that are still deployed and reachable but are protected only by the assumption that users won't know the URL.

For example:

/admin-legacy-panel.php
→ not linked anywhere
→ still accessible directly
→ no authentication or authorization check

The application isn't actually enforcing access control. It's relying on security through obscurity:

"We don't link to it"
        ≠
"Users cannot access it"

The URL may eventually be discovered through many ordinary sources:

JavaScript bundles
→ exposed routes or API paths

sitemap.xml
→ forgotten administrative pages

robots.txt
→ paths developers didn't want indexed

Browser history / logs
→ previously accessed URLs

Documentation / source code
→ endpoint names revealed

Guessing
→ predictable paths such as /admin/, /backup/, /legacy/

Once the path is known, the absence of a link provides no protection:

GET /admin-legacy-panel.php
        ↓
Server checks authentication? ✗
Server checks authorization? ✗
        ↓
Access granted

Hidden functionality is not protected functionality. If an endpoint must be restricted, the server must explicitly enforce the required authorization regardless of whether the URL is visible in the UI.

5

Vulnerability

Forced Browsing

⚠️

Vulnerability

Forced browsing is the act of directly requesting a resource or endpoint that the application expects users to reach only after passing through an earlier step or authorization gate.

For example:

/checkout/confirmation?order=4521
→ normally reached only after successful payment

A vulnerable application might behave like this:

GET /checkout/confirmation?order=4521
        ↓
Server checks: is the order ID valid? ✓
Server checks: has payment actually succeeded? ✗
        ↓
Confirmation page displayed

The attacker simply skips the intended flow and requests the final endpoint directly.

The fundamental mistake is assuming:

User reached this page through checkout
        ↓
Therefore payment must have succeeded

But navigation history is not authorization state. The server must independently verify the condition that makes the operation legitimate.

A secure implementation would do something like:

Request confirmation for order 4521
        ↓
Authenticate requester
        ↓
Verify requester can access order 4521
        ↓
Verify payment status = successful
        ↓
Allow confirmation

This same pattern applies beyond checkout:

Step 1: complete verification
Step 2: access sensitive page

Step 1: complete MFA
Step 2: access account settings

Step 1: create draft
Step 2: publish document

If Step 2 can be called directly without the server verifying that Step 1 actually happened, the sequence is being enforced by convention rather than security logic.

6

Vulnerability

Fail-Open Defaults

⚠️

Vulnerability

A fail-open authorization flaw occurs when an authorization check fails to produce a valid decision, but the application allows the request to continue anyway.

For example:

try {
    if (!userCanAccess(user, resource)) {
        return deny();
    }
} catch (err) {
    // Permission lookup failed
    return next(); // request proceeds
}

The normal paths behave correctly:

userCanAccess() → true
→ allow

userCanAccess() → false
→ deny

But the failure path is dangerous:

userCanAccess()
        ↓
throws / times out / returns invalid result
        ↓
catch block
        ↓
allow request

The critical mistake is treating "authorization could not be determined" as equivalent to "authorization was granted."

For security-sensitive operations, the safer default is fail closed:

Authorization succeeds → apply the decision
Authorization explicitly denies → reject
Authorization fails / unavailable / ambiguous → reject

You've completed Authorization and Access Control

Great work — explore other topics to keep learning.