Broken Function Level Authorization (BFLA)
detect, understand, remediate
BFLA is the function-level authorization failure where a privileged or administrative API endpoint accepts a request from a caller that should not be allowed to invoke it. Authentication passes and the route resolves, but the role, scope, or tenant check that should gate the action is missing or only enforced in the UI. Ranked fifth on the OWASP API Security Top 10 (API5:2023), BFLA is the action-level analogue of BOLA and a frequent path to vertical privilege escalation in modern multi-tenant APIs.
No credit card required. Free plan available forever.
What is broken function level authorization (BFLA)?
Broken function level authorization (BFLA) is an API authorization failure where a privileged or administrative endpoint accepts a request from a caller that should not be allowed to invoke it. The route resolves, the framework matches the controller, and the handler runs. The role, scope, or tenant check that should sit between authentication and the action is missing, partial, or only enforced in the user interface. Authentication is not the problem. The problem is what the authenticated principal is permitted to do once they have a valid token.
OWASP ranks BFLA fifth on the API Security Top 10 (API5:2023). It is the function-level analogue of BOLA. BOLA fails to verify that a caller can access a specific object by ID. BFLA fails to verify that a caller can invoke a specific function at all. The two failures often coexist on the same API: a low-privilege token reaches an admin route (BFLA) and from there reads or modifies any tenant object (BOLA). The combined impact is vertical and horizontal privilege escalation in one request. The third member of the OWASP API authorisation trio, BOPLA, sits one level deeper still: once the function call is allowed and the object is loaded, BOPLA is the field-level decision about which properties the principal may read and which they may write.
BFLA is most common in APIs that grew from a single-role product into a multi-role product without a formal authorization redesign. New admin functions get built behind the same router, the UI adds role-based menu hiding, and the team assumes the API already enforces the role because the authentication layer requires a valid token. The token is valid; the function check is missing. A regular user account, an anonymous-but-authenticated trial token, or a service account scoped to a different tenant can now call administrative functions that the product owner believes are restricted.
BFLA vs BOLA vs BOPLA: where the line sits
The OWASP API Security Top 10 separates three authorization classes that share a root cause but fail at different layers. A clean finding pins the failure to one of them; a sloppy finding conflates two and the remediation guidance applies to neither. The table below names the practical distinctions.
| Dimension | BOLA (API1) | BFLA (API5) | BOPLA (API3) |
|---|---|---|---|
| What fails | Object access check by ID. | Function or action invocation check by role or scope. | Field-level read or write check inside an authorised object. |
| Typical surface | GET, PUT, DELETE on /resource/:id with another tenant's ID. | POST, PUT on /admin, /internal, /reports, /export reachable from a non-admin token. | PATCH that sets isAdmin, role, tenant_id, balance, status when those fields should be server-controlled. |
| Catalog | OWASP API1:2023, CWE-639 / CWE-285. | OWASP API5:2023, CWE-285. | OWASP API3:2023, CWE-915 / CWE-213. |
| Typical impact | Cross-tenant data exposure or modification through automated ID enumeration. | Vertical privilege escalation, admin actions from a normal account, tenant isolation breach. | Quiet self-promotion to admin, silent change of tenancy, balance or status manipulation. |
| Where the fix lives | Database query with tenant or owner predicate, plus authorisation policy on the read path. | Per-route role and scope check tied to the verified session, not the URL prefix or UI menu. | Server-side allowlist of writable fields per role and per object state. |
How it works
Authenticate as a regular user
The attacker creates a normal-tier account on the SaaS application or trial environment. They authenticate, capture a valid bearer token, and inspect API traffic to learn the route shapes from their own session.
Discover privileged routes
They look for routes named /admin, /internal, /reports, /export, /impersonate, or /system. Sources include the front-end JavaScript bundle, the OpenAPI specification if exposed, public documentation, GitHub Issues, and HTTP 404 differentials between guessable and non-guessable paths.
Replay with the low-privilege token
They call those routes with their own normal-user token. If the API resolves the request without a role or scope check, the response is the privileged action: a user listing, a billing export, a tenant-wide setting change, an account impersonation token.
Walk method and parameter variants
They walk GET, POST, PUT, PATCH, DELETE on each suspected route. They flip parameter names like role=admin or tenant_id to other values. The attacker who finds one BFLA is usually one HTTP method or parameter variant away from finding ten more.
Common causes
Authorization in the UI, not in the API
The front end hides the admin menu when the user is not an admin. The admin endpoints behind the menu work for any authenticated principal because the team assumed the UI gating was sufficient. A single curl request bypasses the entire control.
Path-prefix gating without per-route checks
A middleware checks for a role only when the path begins with /admin. Any privileged route that does not live under that prefix (a renamed /reports endpoint, an embedded /api/v2/system path, an internal /tasks route) falls through with no role check.
Trusting a client-supplied role claim
The API reads role from a header, query string, or unsigned JWT field that the client controls. Any caller can set role=admin and inherit the privilege. The fix is to read the role from a verified session lookup, not from a client-influenced value.
Implicit tenant from a body field
An admin endpoint takes tenant_id in the request body. The handler operates on the supplied tenant rather than on the authenticated principal's tenant. A low-privilege token from tenant A passes tenant B in the body and acts on tenant B's data.
Internal endpoints reused for tenant traffic
A back-office endpoint built for the operations team is reachable from the public ingress because the API gateway routes by path rather than by audience. Tenants can now hit a route the product team treats as internal.
Verb confusion on a single route
A route enforces a role on POST but not on PATCH or DELETE. The team added the role check when the create handler shipped and forgot when the modify and delete handlers were added later. The route looks authorised; one verb is not.
How to detect it
Automated detection
- SecPortal's authenticated scanner replays admin and privileged routes captured from a high-privilege session against a low-privilege token, comparing HTTP status, response shape, and response size to surface role-separation gaps.
- Method-walking probes try every HTTP verb on every captured route, since BFLA frequently appears on PATCH, PUT, or DELETE while the GET handler is correctly gated.
- Code scanning surfaces controllers and route registrations that lack a role decorator, policy guard, or scope check on the same handler that touches privileged data, a signature that maps cleanly to the BFLA root cause.
Manual testing
- Provision two roles in the same tenant: a regular user and an admin. Capture the API traffic for the admin user, then replay every recorded request with the regular-user token. Each call that succeeds without a 403 or 404 is a BFLA candidate.
- Walk the OpenAPI specification, the front-end JavaScript bundle, and the route table from the framework. Any privileged endpoint that does not appear in the regular-user UI is a candidate to call directly with a regular-user token.
- Test verb variants on every authorised route. PATCH /users/:id with role=admin in the body. PUT /tenants/:id/settings with another tenant in the path. DELETE /api-keys/:id from a non-owning user. The verb-method matrix is where most BFLA findings hide.
- Inspect impersonation, support, and back-office endpoints. They almost always exist, almost always run with elevated capability, and frequently sit behind only an authentication check rather than a role check.
How to fix it
Move authorization to a per-route policy that depends on the verified session
Every privileged route should call a check(actor, action, resource) policy that resolves the role and tenant from the server-side session, not from a client-supplied header, body field, or path prefix. The policy returns allow or deny; the handler refuses to run on deny. This makes the authorisation decision auditable and consistent across handlers.
Default-deny new routes
Apply a default-deny rule at the router level so any new route requires an explicit allow policy before it returns a 200. New endpoints that ship without authorisation will fail in development rather than in production. A list of patched endpoints is not an architecture; default-deny is.
Test BFLA in CI/CD across role and tenant boundaries
Add integration tests that authenticate as a regular user and call every admin endpoint, asserting a 403. Add tests that authenticate as a tenant A user and call tenant B endpoints, asserting a 403 or 404. New endpoints inherit the test by attribute or path matching so the suite catches missing checks before review.
Eliminate path-prefix authorisation
Stop relying on /admin in the URL as the gate. Authorise per route based on the action, not the path. Privileged routes that live outside /admin (often inadvertently after refactors) lose their accidental gate the moment the prefix middleware is removed.
Read role and tenant from the verified session
The server resolves the actor from the session lookup. The server resolves the tenant from the session, not from a header or body. The client cannot mutate either value to escalate. This single rule eliminates a large class of BFLA failures rooted in client-supplied authorisation context.
Log and alert on denied authorisation attempts
A 403 or 404 from a non-admin principal hitting a privileged endpoint is a behavioural signal. Repeated denials across many privileged routes from one principal is a probing pattern. Without this telemetry, BFLA exploitation is invisible until the data is on a public forum.
How to report a BFLA finding
BFLA findings are easy to demonstrate (a single replayed request) and structurally hard to remediate (because the fix sits in policy, not in input validation). A clean report keeps the proof concrete and the remediation guidance specific to where in the stack the missing check lives.
- Pin the finding to one concrete privileged endpoint and one concrete low-privilege replay. The proof should be reproducible from the report alone.
- Capture the full request and response for both the legitimate admin call and the unauthorised low-privilege call so the difference is obvious.
- Score the finding with a CVSS 3.1 vector that reflects the real impact: privileges required, scope, and confidentiality, integrity, and availability impact change with how broadly the missing check applies.
- Distinguish between a single-endpoint BFLA and a systemic BFLA. A pattern that recurs across many admin endpoints is a single underlying authorisation architecture finding, not many independent issues, and the report should say so.
- Recommend a per-route policy fix and a default-deny posture rather than a per-endpoint patch where the pattern is systemic. A list of patched endpoints does not protect the next privileged route the team ships.
How SecPortal helps with BFLA findings
SecPortal is the engagement record where authenticated scanner output, manual API findings, and remediation tracking live on the same engagement. For BFLA specifically, the platform supports the workflow at four points.
- The authenticated scanner replays admin and privileged routes across role contexts and tenants and flags responses that should not have been served, surfacing function-level authorisation gaps before manual triage starts.
- Findings management lets you log a BFLA finding once with the CVSS 3.1 vector, the OWASP API5:2023 mapping, the captured request and response evidence, and the remediation guidance from the finding template library, then attach every recurring instance back to the same systemic finding rather than creating duplicate records.
- Code scanning via Semgrep surfaces controllers and route registrations that lack a role guard or policy decorator on the same handler that touches privileged data, anchoring the BFLA finding to a specific file and line in the connected repository.
- The branded client portal gives the engineering team the finding, the proof, and the remediation owner in one place. Retests pair to the original finding so the verification record is anchored to the original scope.
For the wider API testing context, see the API security testing checklist and the API security testing use case. For the matching risk taxonomy, see the OWASP API Security Top 10 framework reference. For severity calibration on findings like BFLA, see the research on severity calibration for pentest findings. AppSec teams running this as part of an ongoing programme can read the AppSec teams view to see how BFLA findings sit alongside scanner-result triage, vulnerability prioritisation, and the SDLC handoff.
Where BFLA fits in the AppSec lifecycle
Design review
Threat modelling identifies privileged actions and the role boundary before code is written. The output is the authorisation matrix the implementation has to honour.
Pull request review
New routes that touch privileged data are reviewed against the authorisation matrix. A missing policy guard is a review-blocking issue, not a follow-up ticket.
Pre-release testing
Authenticated scanning and integration tests walk the role matrix on a staging build. Findings reach the engagement record before the route ships.
Production monitoring
Denied-authorisation telemetry feeds the SOC and the AppSec rotation. Repeated 403s from one principal across privileged routes is a probing pattern that warrants investigation.
Incident response
A confirmed BFLA exploitation triggers the incident response process, scope assessment of all routes sharing the same authorisation pattern, and a hold on adjacent releases until the architecture fix is in.
Retest and closure
The retest replays the original low-privilege call against the fixed route, asserts the 403 or 404, and walks adjacent routes to confirm the systemic pattern is resolved before closing the finding.
Compliance impact
Catch BFLA before a low-privilege token finds the admin route
SecPortal's authenticated scanner replays admin and tenant-scoped endpoints across role contexts, captures the request and response pairs as reproducible evidence, and carries the finding through retest. Start scanning for free.
No credit card required. Free plan available forever.