Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui, itaque voluptate ipsa non enim amet ducimus voluptatibus deserunt nam esse!
Testing Internal Dashboards for Authorization Bugs with JavaScript

Testing Internal Dashboards for Authorization Bugs with JavaScript

pr0h0
testingjavascriptauthorizationweb-securitydashboards
AI Usage (85%)

Introduction

Internal dashboards often rely on the frontend to hide sensitive functionality, assuming that if a user can't see an "Admin" button, they can't access admin endpoints. That assumption collapses as soon as someone opens browser dev tools. This post shows you how to test authorization in internal dashboards using nothing but JavaScript in the console—replaying requests with swapped identities, manipulating IDs, checking for mass assignment, and automating the process to surface missing access controls.

Why Authorization Often Fails in Internal Dashboards

Internal tools juggle multiple roles—support, manager, admin—but the backend frequently reuses the same handlers without per-role checks. Visibility toggles happen in React state or v-if directives, not on the server. The same endpoint may return extra fields or allow mutations when called directly with a lower-role token. These bugs are invisible in normal click-only testing.

Recon: Map Endpoints, Roles, and Hidden API Calls

Before you fire off unauthorized requests, map what exists.

Mapping Endpoints and Roles

  • Log in as each role and list visible pages and actions.
  • Collect API endpoint patterns from the Network tab.
  • Identify endpoints that only appear when logged in as a higher role (e.g., /api/admin/settings only shows for admins).

Extracting Hidden API Calls from the Frontend

Endpoints that don't render for lower roles are often still discoverable. Open Chrome's Network tab while logged in as admin and record all requests. Then:

  1. Right-click a request → CopyCopy as fetch.
  2. Paste into the console while logged in as a lower role.
  3. Replace the token or session cookie and execute.
💪

Copy as fetch gives you a ready-to-run call with headers and body intact—just drop it in the console and swap the auth.

Testing Authorization Bugs Directly in the Console

Now you can verify whether the server actually enforces access controls.

Replaying Requests with Swapped Identities

Grab a fetch call that works for admin, swap in a lower-role token or cookie, and replay it.

fetch("/api/admin/audit-logs", {
  headers: {
    Authorization: "Bearer <viewer-token>",
  },
})
  .then((r) => r.json())
  .then((data) => console.log(data));

If you get records that should be admin-only, the server is letting the client decide.

Manipulating IDs and Role Parameters

Alter route or query parameters to see data belonging to other users or roles. Common payloads:

  • /api/users/123/api/users/124
  • /api/tasks?assigned_to=meassigned_to=admin
  • /api/roles/update with {"userId": "me", "role": "admin"}

A 200 with changed data means IDOR or a missing ownership check.

Checking for Mass Assignment and Unvalidated Permissions

Some endpoints blindly accept extra fields. Send a PATCH to update your own profile and tack on "permissions": "admin" or "isAdmin": true. If the server saves it, the bug is serious.

Automating Checks with a Console Script

Instead of manually replaying every request, loop over endpoints and roles right from the console.

auth-loop.js
const tokens = {
viewer: "vtok...",
editor: "etok...",
admin:  "atok..."
};

const endpoints = [
"/api/admin/settings",
"/api/users/42",
"/api/orders/123",
];

async function check(url, role) {
const res = await fetch(url, {
  headers: { Authorization: "Bearer " + tokens[role] },
});
console.log(role.padEnd(8), url, res.status, await res.clone().text().slice(0, 200));
}

(async () => {
for (const url of endpoints) {
  for (const role of Object.keys(tokens)) {
    await check(url, role);
  }
}
})();

When a viewer gets a 200 on /api/admin/settings, you've found a missing role gate.

A Real-World Example: Exposed Revenue Data

I've seen this pattern firsthand: a support-dashboard user could call /api/analytics/revenue directly and get back the same monthly revenue as the finance team. The API had zero server-side role checks—the frontend just didn't render the "Analytics" link for the support role. Impact: sensitive financial data exposed to every support agent.

Defenses and Fixes

All fixes belong on the backend:

  • Check the caller's role inside every endpoint. Don't assume the UI hid a button.
  • Verify ownership. Compare request.user.id to the resource owner, not just that a token is present.
  • Whitelist input fields. Reject unknown keys in update payloads; never dump the request body straight into the model.
  • Return consistent errors. Use 403 with a generic message, not status-code differences that leak endpoint existence.
  • Centralize authorization logic. Use middleware or policies, not scattered if checks in every handler.

Conclusion

Internal dashboards are full of authorization bugs once you stop trusting the UI. Compare what an admin sees versus what a low-role user can call directly—the gaps become obvious. Fixing them is straightforward, but finding them requires testing as if the frontend doesn't exist.

Share this post

More posts

Comments