← Back to API Security for AI-Assisted Development
API5:20234 min read read

Broken Function Level Authorization (BFLA)

Ensure regular users cannot reach admin or privileged endpoints — function-level authorization for every route.

URL: trusteed.io/academy/api-security/broken-function-level-authorization

Broken Function Level Authorization (BFLA) occurs when an API fails to properly restrict which users can invoke which functions — allowing a regular user to reach an admin-only endpoint, or a read-only role to trigger a state-changing operation, simply because the authorization check for that specific route or HTTP method is missing or misapplied.

What Is BFLA?

Where BOLA (API1) is about accessing the wrong object, BFLA is about accessing the wrong function or endpoint entirely — regardless of which object is involved. Common patterns include: administrative endpoints reachable by any authenticated user because authorization middleware wasn't applied to that specific route; APIs that check the user's role for GET requests but not for POST/PUT/DELETE on the same resource, assuming (incorrectly) that write access was already gated elsewhere; and role checks that trust a client-supplied value (a header, a body field, or a role embedded in a JWT the server doesn't re-verify against the current database state).

Why AI Coding Assistants Get This Wrong

Authorization middleware is often applied inconsistently across a route file — added to the routes a developer was thinking about "obviously need protection" (delete, admin panel) and forgotten on ones that seem lower-risk (a bulk-export endpoint, an internal debug route, a newly added feature endpoint). AI assistants extending an existing route file tend to follow the pattern of neighboring routes; if authorization middleware is inconsistently applied in the existing code, the assistant will often replicate that inconsistency rather than correct it, because it has no way to know the intended authorization boundary unless it's stated as an explicit, centrally enforced rule.

Vulnerable Pattern

// Role check only applied to some routes in the same resource — inconsistent, easy to miss extending
app.get('/api/admin/users', authenticate, requireAdmin, listUsers);
app.get('/api/admin/users/:id', authenticate, requireAdmin, getUser);
app.delete('/api/admin/users/:id', authenticate, deleteUser); // requireAdmin forgotten here

// Trusting a client-supplied role instead of re-checking server-side
app.post('/api/reports/export', authenticate, (req, res) => {
  if (req.body.role === 'admin') { // attacker can just send { role: "admin" }
    return exportFullReport(res);
  }
  exportLimitedReport(res);
});

Secure Pattern

// Centralized, route-level enforcement — applied once, per resource group, not per handler
const adminRouter = express.Router();
adminRouter.use(authenticate, requireAdmin); // every route under this router is protected, by default

adminRouter.get('/users', listUsers);
adminRouter.get('/users/:id', getUser);
adminRouter.delete('/users/:id', deleteUser); // inherits requireAdmin automatically

app.use('/api/admin', adminRouter);

// Role derived from the verified session/token, never from client input
app.post('/api/reports/export', authenticate, (req, res) => {
  if (req.user.role === 'admin') { // req.user comes from verified auth middleware, not req.body
    return exportFullReport(res);
  }
  exportLimitedReport(res);
});

Grouping privileged routes under a single router with authorization middleware applied once, at the router level, eliminates the "forgot to add it to this one route" failure mode entirely — new routes added under /api/admin are protected by default rather than by developer memory.

Copy-Paste Rules for Cursor, Claude Code, and Codex

---
description: BFLA prevention — function/endpoint-level authorization enforcement
alwaysApply: true
---

# Function Level Authorization Rules

- Group privileged routes under a dedicated router/blueprint/controller with
  authorization middleware applied ONCE at the group level, so new routes are
  protected by default rather than requiring per-route memory.
- Never derive a user's role or permission level from client-supplied input
  (request body, query string, custom headers). Always read it from the
  authenticated session or a freshly verified token claim, cross-checked
  against the current database state if roles can change.
- Apply the same authorization check across ALL HTTP methods for a given
  resource (GET, POST, PUT, PATCH, DELETE) — do not assume that protecting
  read access implies write access is also protected.
- When adding a new endpoint to an existing route file, do not assume the
  existing pattern of authorization middleware is correct — check explicitly
  whether the new endpoint requires the same, stricter, or different
  authorization than its neighbors.
- Any endpoint intended for internal, debug, or administrative use only must
  have explicit authorization middleware — "not linked from the UI" is never
  a valid access control.
- Deny by default: new routes should require explicit authorization
  configuration to be reachable by any role, rather than being open by
  default and requiring explicit restriction.

Verification Checklist

  • Privileged routes are grouped under routers/middleware applied at the group level, not per-handler
  • Authorization is enforced identically across all HTTP methods for a given resource
  • No role or permission check trusts client-supplied input
  • Every "internal" or "debug" endpoint has explicit authorization, regardless of whether it's linked from a UI
  • Automated tests attempt every state-changing endpoint with a lower-privileged role and confirm rejection
  • Continuous testing validates function-level authorization at runtime — see Trusteed's WAAP platform for automated and manual API testing that probes authorization boundaries

Frequently asked questions

Does hiding an endpoint from the UI (not linking to it) provide any real protection?

No. "Security through obscurity" — an endpoint that exists and is reachable but simply isn't linked from any visible UI — provides no actual protection, since the endpoint's URL can be discovered through API documentation, client-side JavaScript bundles, or simple guessing.

Is BFLA more common in REST or GraphQL APIs?

BFLA affects both, but GraphQL requires particular care because a single endpoint (`/graphql`) can expose many distinct "functions" as resolvers — authorization has to be enforced per-resolver or per-field, since URL-level route protection doesn't apply the same way it does in REST.

Can API gateways handle BFLA enforcement instead of application code?

Gateways can enforce coarse-grained authorization (e.g., blocking a role from an entire path prefix), which is valuable as defense in depth, but fine-grained, business-logic-aware authorization generally still needs to be enforced in application code, since gateways typically lack full context about resource-specific rules.