API Security in Depth · Function Authorization, Abuse & SSRF
Broken Function Level Authorization
Deny-by-default route permissions and hidden admin endpoints.
BFLA: a regular user can call functions they shouldn't — admin endpoints, other roles' actions, or methods (DELETE where only GET was intended). Causes: authorization added per-handler (and forgotten), "hidden" endpoints assumed safe, and inconsistent checks between REST and GraphQL/gRPC paths. Fix with deny-by-default routing, declarative permissions per route, separate admin APIs (different host/auth), and a route inventory test that fails if any route lacks a policy.
BFLA anti-patterns
| Pattern | Risk | Better |
|---|---|---|
| Admin endpoints on the public API | Discoverable, same auth | Separate admin API + network restrictions + step-up auth |
| Checks inside handlers | Missed on new routes | Route-level declarative policy |
| Allow by default | New routes are open | Deny by default |
| Role in request body | Client-controlled | Roles from token / server-side lookup |
| UI hides buttons | API still callable | Enforce in API |
def test_every_route_declares_permission():
missing = [r.path for r in app.routes
if isinstance(r, APIRoute) and not getattr(r.endpoint, "__permission__", None)
and r.path not in PUBLIC_ROUTES]
assert not missing, f"Routes without a permission: {missing}"
def requires(permission: str):
def deco(fn):
fn.__permission__ = permission
return fn
return deco
@Controller("admin/users")
@UseGuards(AuthGuard, PermissionsGuard) // guard denies when no @Permissions metadata
export class AdminUsersController {
@Delete(":id")
@Permissions("users:delete")
remove(@Param("id") id: string) { return this.users.remove(id); }
}
http.authorizeHttpRequests(a -> a
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/v1/**").hasAuthority("SCOPE_write")
.requestMatchers("/v1/public/**").permitAll()
.anyRequest().denyAll()); // deny by default
Common pitfalls
- Relying on obscure URLs for admin functions.
- Different authorization in REST and GraphQL for the same operation.
- Method confusion (PUT allowed where only GET is checked).
Key points
- Deny by default
- Declarative permission per route
- Test that every route has a policy
Further reading: OWASP API5:2023 BFLA
Practice questions
How is BFLA different from BOLA?
Answer
BOLA is accessing an object you shouldn't (another user's order); BFLA is calling a function you shouldn't (an admin delete endpoint).
What automated test catches missing function-level authorization?
Answer
A test that enumerates all registered routes and fails if any non-public route lacks a declared permission/policy, plus role-matrix tests per endpoint.
Open in the app to mark lessons complete, take notes and track your progress.