BFLA Testing

BFLA testing

Broken function level authorization is what lets a normal user reach an administrator function. This page explains what BFLA is, how it differs from BOLA, how attackers reach privileged functions, and how Operator proves it by sending privileged-function requests from lower-privileged roles and confirming the action succeeds.

Written by Berk Dusunur, Founder & CEO, Planck Proof · Updated September 2026

Definition

What BFLA is

Broken function level authorization, listed as API5:2023 in the OWASP API Security Top 10 and mapped to CWE-285, occurs when an API does not enforce role and privilege checks at the function or endpoint level. The server confirms the caller is authenticated, but never confirms the caller role is permitted to perform this particular action.

It shows up most often on functions that no ordinary client interface calls directly: an administrative panel route left reachable from any account, a bulk export a support tool assumes only staff will find, a role-change endpoint built for an internal ops dashboard. None of those are secret to the server, only to the people clicking through the product, and a request library does not know the difference.

The gap

No check on the action

The function executes because the request reached it, not because the caller was authorized to invoke it. Role enforcement is assumed to happen in the client, or is applied inconsistently across routes and methods.

Privilege escalation

Standard user, admin power

The result is vertical escalation: a normal account performs operations reserved for administrators or privileged service roles, such as promoting users, changing configuration, or deleting other accounts.

Hidden routes

Hidden is not protected

Administrative routes omitted from the client interface are still reachable over HTTP. If the only thing hiding a function is the absence of a button, it is not access control.

BFLA vs BOLA

Function versus object

The two authorization flaws at the top of the OWASP list are easy to conflate and important to separate. BOLA is authorization on the data object: can you reach a record that belongs to someone else. BFLA is authorization on the function: can you invoke an operation your role should never be able to perform.

BOLA usually moves horizontally, between peers at the same privilege level. BFLA usually moves vertically, escalating a standard user into administrative capability. An API can have one, the other, or both, which is why we test them as distinct classes. For a side-by-side treatment, see our writeup on BOLA versus BFLA authorization flaws and the companion BOLA testing page.

  • BOLA asks: is this object mine to access.
  • BFLA asks: is this function mine to call.
  • BOLA moves horizontally between equal peers.
  • BFLA moves vertically, from user to admin.
  • Both stem from authorization the server fails to enforce.
Exploitation

How BFLA is exploited

An attacker starts from a legitimate low-privileged account and probes for functions the role should not reach. The techniques are simple and effective when the server routes on path and method without re-checking the caller.

Direct calls

Invoke privileged endpoints

Call administrative endpoints directly with a normal user token. If the function runs, the role check was missing or trusted the client to hide the route.

Method swap

Change the HTTP method

Turn a permitted GET into a PUT, PATCH, or DELETE on the same path. Frameworks often protect the method the client uses and leave the others open.

Route discovery

Guess admin routes

Enumerate conventional administrative paths, versioned variants, and internal routes that never appear in the client but remain live on the server.

Methodology

How to test for BFLA, step by step

Many vendors publish a comparison of BOLA and BFLA. Few publish a standalone methodology for testing BFLA on its own, function by function and role by role. This is the sequence we run, whether it is a person working through it by hand or Operator automating it against your specification.

Step 1

Build the function inventory

List every function the API exposes from its OpenAPI or GraphQL specification, including admin, export, billing, and bulk operations, then add any undocumented or internal route surfaced during crawling. A function missing from the inventory cannot be tested for BFLA.

Step 2

Define the role matrix

List every role and tenant type in scope and state, for each function, whether that role should be permitted to call it. This matrix is the ground truth for the test: a mismatch between what it says and what the API actually allows is a BFLA finding.

Step 3

Test per role, per method

With one bearer token per role, call every function as each lower-privileged role, then repeat across GET, POST, PUT, PATCH, and DELETE on the same route. A role check enforced on the method a client happens to use is frequently absent on the others.

Step 4

JWT claims and GraphQL mutations

Decode the bearer token and tamper with any role or scope claim it carries to see whether the server re-verifies the claim server-side or trusts what the client sent. On GraphQL, test every mutation individually, including ones surfaced only through introspection, since one endpoint can expose dozens of distinct privileged operations behind a single schema.

Step 5

Multi-tenant admin functions

On multi-tenant APIs, test whether an admin scoped to one tenant can invoke a function against a different tenant's data. Both callers may hold the identical role name, so this is a tenant-boundary failure distinct from, and easy to miss alongside, a role-boundary failure.

Step 6

Confirm impact

Do not stop at the response code. Refetch the record with an authorized token, or otherwise verify the state actually changed, so the finding is a proven successful action rather than a lenient status code that changed nothing underneath.

Step 7

Remediate and retest

Push each confirmed gap to deny-by-default, server-side enforcement, then retest the exact same role and method combination once the fix ships to confirm the check actually holds under load.

Role Matrix

A real role-authorization matrix

Step 2 above asks for a role matrix before any request is sent. Here is a representative one for a small commerce API with four roles, Guest, User, Support, and Admin, against six functions including an administrative export and a role-change endpoint. Cells marked Allow are the intended, correct behavior; the test is whether the live API matches this table for every cell, not just the ones a client happens to call.

FunctionGuestUserSupportAdmin
GET /v1/productsAllowAllowAllowAllow
POST /v1/ordersDenyAllow, own orders onlyAllow, read any orderAllow
GET /v1/users/{id}DenyAllow, self onlyAllow, any userAllow
PATCH /v1/users/{id}/roleDenyDenyDenyAllow
POST /v1/payments/{id}/refundDenyDenyAllow, capped amountAllow
GET /v1/admin/exportDenyDenyDenyAllow

The two rows most worth testing first on any API are the role-change endpoint and the bulk export: they are the highest-impact functions and the ones most often left with an incomplete check because no normal client interface calls them directly.

Building this table takes an hour with whoever owns the API, and it turns BFLA testing from an open-ended search into a fixed checklist: six functions times four roles is twenty-four calls to make and compare against twenty-four expected outcomes. Scale that to a real API with a few hundred operations and a handful of roles, and the number of cells grows fast, which is exactly why enumerating the full matrix from a specification and running every cell on every deploy is better suited to an agent than to a person re-deriving it by hand each quarter.

How Operator tests it

Testing BFLA by role

You provide one bearer token per role. Operator enumerates every function the API exposes from its specification, then sends each privileged-function request from a lower-privileged role and confirms whether the action actually succeeds, rather than assuming a 403 that never comes.

It exercises alternate HTTP methods on every route, tests undocumented and administrative paths uncovered during discovery, and works from the spec so functions no current client calls are still checked. Because it reruns continuously, a role check dropped by a new deploy is caught quickly rather than at the next annual test.

On multi-tenant and GraphQL APIs, the same run also builds the role matrix from step 2 automatically: it treats each tenant's admin as its own identity, tests role and scope claims embedded in the token separately from the transport-level bearer check, and enumerates GraphQL mutations from the schema rather than from whatever a client happens to send, so a mutation with no UI in front of it is still tested like any other function.

  • One token per role is all the access the test requires.
  • Every function enumerated from the spec, not sampled from a crawl.
  • Privileged calls from low roles to confirm the action truly succeeds.
  • Alternate methods and hidden routes exercised on every path.
  • Continuous reruns so a dropped role check surfaces quickly.
Example

What a proven finding looks like

Illustrative example, not a specific customer result. A standard user calls an administrative function that promotes an account to admin, using safe placeholder values throughout.

POST https://api.example.com/v2/admin/users/3310/roles HTTP/1.1
Host: api.example.com
Authorization: Bearer <standard-user-token>
Content-Type: application/json
{ "role": "admin" }

HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 3310, "role": "admin", "updated": true }

The token belongs to a standard user with no administrative rights, yet the privileged role-assignment function executed and returned success. The finding ships with the request, the successful response, and a baseline showing the same call denied for the role the API intends to gate, so the escalation is reproducible and verifiable on retest. It can be replayed directly with curl to confirm it outside any tooling:

curl -s -X POST https://api.example.com/v2/admin/users/3310/roles \
  -H "Authorization: Bearer <standard-user-token>" \
  -H "Content-Type: application/json" \
  -d '{ "role": "admin" }'

When BFLA chains into BOLA

The two flaws often meet inside the same attack. Here, the standard user does not stop at self-promotion: the same missing role check also protects GET /v2/admin/users, a function that returns every account's records rather than the caller's own. Once the function-level gap grants admin capability, every object-level boundary the admin role was trusted to bypass on the client's behalf collapses with it, and a single BFLA finding becomes a full-tenant BOLA exposure. This is why we test function and object authorization as distinct classes but report the chain when one enables the other, rather than stopping at the first successful call.

The reverse chain happens too: a BOLA gap that leaks another user's internal identifier can then be fed into a function scoped to that identifier, turning object exposure into a privileged action the attacker was never meant to reach. Either direction, the fix is the same, an explicit server-side check on both the object and the function for every request, so closing one boundary does not leave the other standing open.

Remediation

How to fix and prevent BFLA

Deny by default and enforce an explicit role check on every function on the server. Authorization must be based on the authenticated identity and its role, applied consistently across all HTTP methods for a route, and it must never depend on the client hiding a control.

  • Deny by default so a function without an explicit grant is unreachable.
  • Check the role server-side on every function, for every HTTP method.
  • Do not rely on hidden routes; obscurity is not authorization.
  • Centralize enforcement so new endpoints inherit the role check.
  • Separate admin surfaces and gate them behind explicit privilege.
FAQ

Common questions about BFLA

What is BFLA?

BFLA, broken function level authorization, is listed as API5:2023 in the OWASP API Security Top 10. It occurs when an API fails to enforce role and privilege checks at the function or endpoint level, so a lower-privileged user can invoke a function reserved for administrators or other privileged roles. It is authorization on the action, whereas BOLA is authorization on the data object.

What is the difference between BFLA and BOLA?

BOLA is about objects: can you reach a data record that belongs to someone else. BFLA is about functions: can you invoke an operation your role is not permitted to perform. BOLA moves horizontally between peers, while BFLA typically moves vertically, escalating a standard user into administrative capability. An API can be vulnerable to one, the other, or both.

How is BFLA exploited?

An attacker authenticates as a normal user and then calls privileged endpoints directly, guesses administrative routes that the client hides, or changes the HTTP method on a known path, for example turning a permitted GET into a DELETE or a PUT the interface never exposes. If the server routes on the URL and method without re-checking the caller role, the privileged action succeeds.

How do you test for broken function level authorization?

Build an inventory of every function from the API specification, then define a role matrix stating which roles may call each one. Using one token per role, call every function as each lower-privileged role and repeat across HTTP methods, since a check enforced on one verb is often missing on the others, then confirm impact by verifying the state actually changed rather than trusting the status code alone. Operator automates this: it enumerates every function from your specification, replays each privileged-function request from a lower-privileged role, exercises alternate methods and undocumented routes, and confirms whether the action truly succeeded.

Is BFLA the same as privilege escalation?

They overlap but are not identical. BFLA is the missing authorization check that makes escalation possible: the server fails to verify that a caller's role is permitted to invoke a given function. Privilege escalation is the broader outcome, gaining more access than intended, which can also happen through other means, such as a client-supplied role claim the server trusts without re-verifying it, or a misconfigured identity provider. Most BFLA findings are a form of vertical privilege escalation, but not every privilege escalation traces back to BFLA.

How do we fix BFLA?

Deny by default and enforce an explicit role check on every function on the server, not in the client. Authorization should be based on the authenticated identity and its role, applied consistently across all HTTP methods for a route, and centralized so that new endpoints inherit enforcement rather than each reimplementing it.

Get Started

Prove whether a user can act as an admin

Give us one token per role and the agent will send every privileged-function request from a lower-privileged role, then hand you reproducible evidence of anything that succeeds.