Permission and Authorization Bugs

Hidden UI Action Still Works Through API

When a button or action is hidden from lower-privileged users in the UI, but the underlying API endpoint does not enforce the same permission server-side, the restricted action can be executed by any authenticated user who calls the API directly. Authorization is only enforced at the presentation layer, not on the server.

HighIntermediateSecurity testingAPI testingManual testing

// UNDERSTAND

// Symptoms

  • A button or menu item is absent from the UI for a Viewer role, but the corresponding API call returns 200 for that same user
  • A Viewer or Editor can delete, publish, or approve records by replaying API requests captured from DevTools
  • The UI hides the Delete button for non-admin users, but DELETE /api/posts/{id} succeeds for any authenticated user
  • Role restrictions enforced in the frontend are absent from the backend

// Root Cause

  • The frontend conditionally renders action elements based on the user's role, but the backend API handler treats any authenticated request as authorized without checking the role
  • Authorization is implemented as a presentation concern — show or hide the button — rather than as a server-side enforcement rule on every request
  • The frontend and backend apply different, inconsistent permission models that are maintained independently

// Where It Appears

  • Admin-only actions such as deleting records, publishing content, or approving requests
  • Feature tier or subscription gates that hide premium actions in the UI
  • Multi-tenant applications where tenant-level restrictions are applied only in the frontend
  • Role-based access systems built by teams that treat the API as an internal-only surface

// REPRODUCE & TEST

// How to Reproduce

  1. 01Log in as Admin in one browser session; perform the restricted action (e.g. delete post 789) and capture the API request from DevTools — note the endpoint, HTTP method, URL, and request body
  2. 02Log out
  3. 03Log in as a lower-privileged user (Viewer); from any authenticated request in DevTools, copy the Viewer's bearer token
  4. 04Construct the captured request (DELETE /api/posts/789) using the Viewer's bearer token
  5. 05Send the request
  6. 06Observe the response and verify whether the post still exists via GET /api/posts/789

// Test Data Needed

  • Two accounts: one Admin and one Viewer (or the lowest role that should be restricted)
  • A resource the Admin can act on but the Viewer should not be able to modify
  • A way to replay API requests with a specific bearer token (DevTools, Postman, or curl)

// Manual Testing Ideas

  • Log in as Viewer, open DevTools Network, replay API calls visible in an Admin's session
  • Test every HTTP method (POST, PUT, PATCH, DELETE) that the UI hides for the current role
  • Attempt the restricted action directly via the API URL without using the UI
  • Verify the error response on a denied request does not leak resource data
  • Test privilege escalation: can a Viewer pass an admin role flag in the request body to elevate their access?
  • Test whether the permission is rechecked if the user's role changes mid-session

// API Testing Ideas

  • Authenticate as Admin; perform the restricted action and capture the exact API request — endpoint, method, and body
  • Authenticate as Viewer; capture the Viewer's bearer token
  • Send the restricted action request using the Viewer's token
  • Assert the response is 403 Forbidden
  • Query the resource state to confirm no mutation occurred (the post was not deleted)
  • Test the boundary case: role just below the required threshold (e.g. Editor vs Admin), and assert 403 for that role as well

// Automation Idea

Automate a two-role flow: authenticate as Admin, capture a restricted endpoint URL and request shape, then authenticate as Viewer and replay the same request with the Viewer's token. Assert HTTP 403. Query the resource state after the attempt to confirm no mutation was applied.

// Expected Result

API calls to restricted endpoints using a lower-privileged user's token return 403 Forbidden, regardless of whether the UI hides the action.

// Actual Result (Example)

A DELETE request to /api/posts/789 using the Viewer's bearer token returns 200 OK and the post is permanently deleted, even though the Delete button is not shown in the Viewer's UI.

// REPORT IT

Example Bug Report

Title
Viewer role can delete posts by calling the delete API directly
Severity
High
Environment
Staging environment Chrome 124 Admin account + Viewer account
Steps to Reproduce
  1. 01Log in as Admin and delete post 789 via the UI; capture the DELETE /api/posts/789 request from DevTools
  2. 02Log out
  3. 03Log in as Viewer; copy the Viewer's bearer token from any authenticated request in DevTools
  4. 04Send DELETE /api/posts/789 using the Viewer's bearer token
  5. 05Observe the response and verify whether the post still exists via GET /api/posts/789
Expected Result
The API returns 403 Forbidden and the post remains intact.
Actual Result
The API returns 200 OK and the post is permanently deleted.
Impact
Any authenticated user who knows the API endpoint can bypass role restrictions and perform privileged or destructive actions, regardless of their assigned role.

// RELATED