Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 5 min read

Authentication vs Authorization: Reaching an Admin Panel Without the Password

Authentication vs Authorization: Reaching an Admin Panel Without the Password TL;DR: The login worked the entire time. The button that led to the admin panel didn't render for my role, the route behind it still answere

Authentication vs Authorization: Reaching an Admin Panel Without the Password

TL;DR: The login worked the entire time. The button that led to the admin panel didn't render for my role, the route behind it still answered, and the server never asked what my role was allowed to do. Authentication gets you in; authorization decides what you can do once you're in, and the app only did the first one.

A review that wasn't supposed to turn into this

A colleague asked me to review a web app he'd built for a restaurant. I'd been developing longer than him and he wanted another set of eyes. I've been building web software for over eight years β€” twelve counting university β€” and security was always on my list, but as an abstract checklist rather than something I'd ever looked at from the attacker's side.

The app was his, he asked me to look at it, and that's the whole context. What follows is the part where the review stopped being about code.

Worth saying up front: this wasn't a beginner's mistake. I'd used RBAC without exception, with separate middlewares for authentication and for role. What I never did was ask the attacker's question β€” is there a route where nobody checks?

Users, sessions, roles

Not a landing page. Users signed in, the app knew who they were, sessions persisted, and each user had a role that decided what they could see and do. There was an admin panel built into the same site.

Standard design, and the design I've shipped myself. Nothing about it surprised me.

Authentication was solid

I checked the base first, because if the login were broken nothing else would matter. Users could authenticate, the server knew who each one was, the session persisted across requests, and the app behaved differently depending on who was signed in. All of it worked.

That was my big misreading of the review. I conflated "people can authenticate" with "this is fine."

The first clue: a button that didn't render

The app had pages that didn't appear for certain roles. The button didn't render. The link wasn't in the menu. Perfectly normal UX β€” a regular user doesn't see what isn't theirs.

But that decision was being made on the client. "Don't show" and "don't allow" had become the same decision, made in the browser. The anti-pattern I had written in my own notes as a warning to myself:

<!-- ❌ WRONG: the protection exists ONLY in the client -->
<script>
  var isAdmin = false
  if (isAdmin) {
    // render admin panel
  }
</script>

The button doesn't appear. That doesn't change the route it points to, or what the server returns when someone requests that route directly.

The second step: devtools and the route in the JavaScript

The link didn't render, but the route existed. This is where the browser's devtools come in β€” even when the button doesn't exist on screen, the condition deciding whether to show it ships in the JavaScript that every visitor downloads. I opened the inspector, searched for that logic, and the route was right there.

No guessing, no brute force. I read code the app hands to anyone who visits. I requested the route and the server answered β€” no alarm, because to it the request was ordinary: a valid session, a correctly signed-in user.

The app never decided I wasn't allowed. No check failed. There was no check.

The third step: Burp and the endpoints

I pointed the browser at Burp's proxy and browsed with my normal, non-admin user without touching anything, just watching which requests the interface made on its own. Burp captured them all. Then I replayed them one at a time, and several returned data or actions belonging to a more privileged role.

The administrative endpoints weren't checking the role before responding. They trusted the interface had already filtered what I was supposed to see, and didn't re-check it themselves.

That's broken access control, and it doesn't need anything spectacular. One check living only in the interface is enough. Hiding the button and protecting the server are different jobs, and only the first one had been done.

Worth saying about the tool: Burp decides nothing. It hands you the request already leaving your session and lets you send it again. The failure is exposed when you replay it with a role that shouldn't be allowed and the server answers anyway.

What I actually reached

I was authenticated the whole time. The system always knew who I was. No authentication bypass, no brute force, no session theft.

I used my legitimate session with my legitimate role, and requested directly what the interface wouldn't have let me request. I reached administrator functionality without ever seeing or obtaining the admin's password.

Scope, precisely: I got functionality that didn't belong to my role. I did not get the server, the database, or anyone's credentials. Authorization problem, not a takeover.

And the part I'm still chewing on: it's entirely possible that under a deadline, in one of my own projects, I left a route without its role middleware, or solved something by hiding the button and trusting the calendar. Same pattern.

The two checks, side by side

Authentication Authorization
Question Who are you? What are you allowed to do?
Answered Once, at login Per resource, per action
Here Worked correctly Skipped in several places

Authentication got me in. The backend never asked what that user was allowed to do β€” it assumed that since the interface had already filtered, it could answer.

What the developer should have checked

  • Don't assume hiding a UI element prevents access. Frontend visibility is UX; permission is a different thing.
  • Check the permission on the server, always and everywhere. If the response depends on the role, the response has to decide it with the role, and every sensitive endpoint has to check it itself.
  • Don't assume a rare or "secret" route is protected.

Three layers, and only the third one decides:

Layer What it protects What it has to do
Interface Only what is seen Show or hide elements
Route or proxy Block before rendering Validate the session
Server Verify session and permissions Check the permission on every endpoint

What I took from it

I'm not a security professional, but this wasn't beginner's work either. The gap was never knowledge β€” authentication, authorization, roles and RBAC I'd done many times. The gap was rigor: I carried it as a checklist, and a checklist is exactly what fails when a deadline eats one route's middleware.

The uncomfortable part: the app authenticated correctly, and that's exactly what makes this easy to miss. A broken login takes two minutes to find.

GitHub: https://github.com/Armando284

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.