BFLA: When Users Reach Admin Functions
If Broken Object Level Authorization is about which records a user can reach, Broken Function Level Authorization is about which actions they can perform. The attack is often as simple as changing a verb or a path: a regular user sends the request an administrator would send, and the API — having checked that they are logged in but not that they are allowed — carries it out.
Functions in an API are its operations: delete a user, promote an account, export the customer list, change a price. Each should be gated by a check that the caller holds the role or permission that function requires. BFLA is what happens when that gate is missing or inconsistent — when the only thing standing between a standard user and an administrative action is that they didn't know the endpoint existed.
How the boundary gets crossed
Administrative and privileged endpoints are frequently guessable. If GET /api/users/me exists, an attacker will try GET /api/admin/users. If a user can PATCH their own record, they will try DELETE on someone else's. API structure is predictable, so relying on obscurity — an unlinked path, an undocumented method — is no defence at all.
DELETE /api/admin/users/42 → 200 (should be 403 — caller is not an admin)
The problem compounds when authorization is scattered across handlers. One developer remembers to check the admin role on the user-deletion endpoint; another, adding a bulk-export endpoint months later, does not. Because each check is written by hand in each function, the coverage is only ever as good as the least careful change — and the gaps don't show up in normal use, only under a deliberate probe.
Enforcing function-level authorization
- Deny by default. Every function should require an explicit grant of the role or permission it needs; anything not explicitly allowed is refused. Never make access the default and denial the exception.
- Centralise the check. Enforce authorization in shared middleware or a gateway that every route passes through, rather than trusting each handler to remember. Consistency is the whole game.
- Separate admin surfaces clearly. Group privileged operations behind a boundary with its own enforced role requirement, and confirm that regular tokens cannot invoke them.
- Don't rely on hidden endpoints. Assume every method and path is known. Obscurity delays discovery; it does not authorise anything.
- Test every function with a low-privilege token. Automated tests that replay administrative requests as an ordinary user catch BFLA before release; unauthorised attempts should be logged and alerted on.
BFLA endures for the same reason BOLA does: it is an invariant that must hold on every privileged operation, forever, and hand-written checks erode as an API grows. The durable fix is architectural — make the authorization decision somewhere every request must pass, so a forgotten check fails closed. And since you can only protect the functions you know you expose, a current inventory of every endpoint and its required role is the ground truth the whole scheme depends on.
- As an ordinary user, call an admin-only route directly (for example swap
/user/for/admin/in a request). If it works, the UI was your only access control. - Try a privileged HTTP method —
DELETEorPUT— on an object you can only read. A success means the method, not just the path, is unguarded.
The full, at-scale version — enumerating every route, method and role combination for the function-level gaps a menu never shows — is what our assessment (and SecStudio agents) runs for you.