← Back to Insights

API Security: What Your WAF Can't See

Metasphere Engineering 13 min read

Your customer-facing API returns full account details for any account number submitted by any authenticated user. No ownership check. Just authentication. A security researcher writes a 12-line Python script. Iterates account IDs from 1 to 500,000. Downloads every customer’s balance, transaction history, and personal details. Takes under three hours. The endpoint has been live for years. It passed multiple pen tests. No scanner flagged it because every single request carried a valid JWT.

Valid badge. Wrong room. The front door checked the badge and waved them through. No room in the building checked whether this person was supposed to be there.

BOLA (Broken Object Level Authorization) sits at number one on the OWASP API Security Top 10 for a reason most teams find uncomfortable: finding it requires business context no automated tool has. A WAF sees a valid token, valid endpoint, valid response. Everything looks correct at the network layer. The business logic failure, user A reading user B’s invoice, is invisible to every security tool in the stack. The perimeter alarm didn’t go off because the intruder had a key.

Key takeaways
  • BOLA is the #1 API vulnerability because no scanner can find it. Every request is authenticated, structurally valid, and uses the right HTTP method. The flaw is invisible at the network layer.
  • Authorization belongs at the data layer, not the gateway. WHERE account_id = :user_account_id on every query. No exceptions. No shortcuts.
  • Mass assignment lets attackers escalate privileges by sending fields your API doesn’t expect. Allowlist input fields explicitly.
  • API rate limiting should target per-user patterns, not just global throughput. 100 sequential requests to /accounts/{id} with incrementing IDs is an attack, not traffic.
  • Shadow APIs are the endpoints nobody documented but traffic analysis reveals. Every undocumented endpoint is unmonitored attack surface. Doors nobody put on the floor plan.
BOLA attack: authenticated user iterates object IDs to exfiltrate other users' dataAnimated sequence showing a BOLA attack. A user authenticates and gets a valid token, then requests their own order and receives a 200 OK. They change the order ID and still get 200 OK with another user's data. Iteration through IDs 1003 through 1005 all return 200 OK. An annotation shows 47,000 records exfiltrated with zero alerts. Finally, an authorization check is added that returns 403 Forbidden.BOLA: Broken Object Level AuthorizationUAttackerValid JWT tokenGET/api/orders/1001own order200 OKGET/api/orders/1002not their order200 OKother user's dataGET/api/orders/1003200 OKGET/api/orders/1004200 OKGET/api/orders/1005...200 OK47,000 records exfiltrated. Zero alerts.Every request returned 200 OK with valid data.GET/api/orders/1002Owner CheckJWT.uid == order.uid?403 ForbiddenAuthentication is not authorization. Check ownership on every request.

The Authorization Problem

BOLA consistently ranks as the most critical API vulnerability. Easy to exploit for any authenticated user. Nearly invisible to automated scanning. Most teams think they’re immune until the pen test proves otherwise.

The pattern is dead simple. An API endpoint accepts a resource identifier: GET /api/invoices/84721. The server validates the token, confirms the user is logged in, and returns the invoice. What it never checks: does the authenticated user have any relationship to invoice 84721? An attacker who is a legitimate customer just iterates invoice IDs and reads other customers’ billing data. No zero-days. No fancy tooling. Just a for loop. The lock on the front door works. Nobody checked the rooms.

The Authorization Blind Spot The gap between network-level authentication (“is this token valid?”) and application-level authorization (“can this user access this specific resource?”). WAFs and gateways handle the first. Only application code handles the second. Most API breaches exploit the space between them.

Fixing BOLA requires authorization at the data access layer. The service checks that the authenticated identity has a real relationship to the requested resource. In practice: your query needs a WHERE user_id = $authenticated_user clause, or your ORM scopes by tenant automatically. This lives in application code. The gateway can’t make this call because it doesn’t know the business rules. The front desk can check your badge is real. Only the department head knows whether you belong in the meeting.

Anti-pattern

Don’t: Rely on the API gateway for resource-level authorization. The gateway validates tokens and rate limits. It has no idea which invoice belongs to which user. Asking the bouncer to do the accountant’s job.

Do: Build authorization middleware that every endpoint uses by default. The developer must declare the ownership relationship (e.g., @authorize(resource="invoice", owner_field="user_id")). Make the insecure path require effort. Make the secure path the default.

The zero trust architecture guide covers how this fits into a broader authorization model.

BFLA (Broken Function Level Authorization) is the related failure: users accessing admin endpoints because developers assumed obscurity would prevent discovery. Attackers enumerate API paths with wordlists. Framework-generated endpoints like /actuator, /debug, and /admin are tried on every single target. Security through obscurity. Also known as no security.

Defense-in-Depth Across Layers

BOLA is the headline, but it’s far from the only threat. Effective API security stacks controls so each layer catches what the others miss. Fence, door, room locks, cameras, guards.

LayerControlsWhat It CatchesBypass Means
Edge / WAFDDoS protection, IP reputation, geo-blockingVolumetric attacks, known malicious IPs, bot trafficAttacker uses clean residential IP
API GatewayToken validation (JWT/OAuth), rate limiting, schema validation (OpenAPI)Invalid tokens, quota abuse, malformed requestsValid token, well-formed but malicious payload
Service MeshMutual TLS (mTLS), service identity verificationService impersonation, man-in-the-middle between servicesCompromised service with valid certificate
ApplicationObject-level authorization (BOLA check), function-level authorizationIDOR attacks, privilege escalation, unauthorized data accessNothing (this is the last line of defense)

Skip a layer and you leave a gap attackers will find.

Schema validation at the gateway enforces your OpenAPI spec on every inbound request. Catches mass assignment attacks before they reach application code. If the spec says POST /users accepts name and email, the gateway rejects any request that includes role, admin, or other undocumented fields. Takes 30 minutes to enable on most API gateways. Thirty minutes to block an entire class of privilege escalation attacks. Best ROI in your security budget.

Rate limiting slows enumeration attacks. An attacker iterating invoice IDs at 10,000 requests per second downloads your entire customer database before anyone notices. Throttle to 10 per second and your anomaly detection has time to surface the pattern. Rate limits don’t prevent BOLA. Only proper authorization does that. But they shrink the window for mass data exposure from minutes to hours. Speed bumps don’t replace door locks. They buy time for the guards.

Anomaly detection on API access patterns catches BOLA exploitation even when authorization controls are incomplete. A single user suddenly requesting 500 unique invoice IDs in an hour is an obvious signal. Build alerting on per-user unique resource ID access rates, not just total traffic volume. Total metrics look completely normal while one user quietly downloads everything.

LayerControlCatchesMisses
Edge/WAFDDoS, IP reputation, geo-blockingVolumetric attacks, known-bad IPsAnything with a valid token
GatewayJWT validation, rate limiting, schema validationExpired tokens, mass assignment, brute forceBusiness logic flaws (BOLA)
ApplicationObject-level authorization, ownership checksBOLA, privilege escalationNew endpoints not yet in policy
MonitoringAnomaly detection on access patternsEnumeration, data exfiltrationSlow, low-volume attacks

Investing in application security starts with getting schema validation, rate limiting, and anomaly detection in place before pen tests. Pen testers finding BOLA through unthrottled enumeration is the expected outcome without these controls.

JWT and OAuth Implementation Failures

Token-based authentication is standard. JWT implementations consistently introduce critical vulnerabilities that standard security reviews miss.

The algorithm confusion attack is the one you need to understand cold. JWTs include an alg header declaring the signing algorithm. A server configured for RS256 (asymmetric) can be exploited by an attacker who crafts a token with alg: HS256. If the server uses the RS256 public key as the HMAC secret for the symmetric HS256 verification, it accepts attacker-crafted tokens signed with the public key. The public key. The one anyone can download from your JWKS endpoint. Handing out the master key and calling it public information. Because it was.

The fix: hard-code the expected algorithm in your JWT verification library. Never accept the algorithm from the token header.

# JWT validation - hard-code algorithm, validate all claims
import jwt

def validate_token(token: str, public_key: str) -> dict:
    return jwt.decode(
        token,
        public_key,
        algorithms=["RS256"],   # NEVER accept from token header
        audience="api.yourapp.com",  # Reject tokens for other apps
        issuer="https://auth.yourapp.com",  # Reject dev/staging tokens
        options={
            "require": ["exp", "iss", "aud", "sub"],
            "verify_exp": True,  # Some libraries skip this by default
        }
    )

Not validating the exp claim is equally common and easier to overlook. Some older JWT libraries don’t enforce token expiry by default. Don’t assume your library handles this. Check the docs. Write a test that verifies an expired token gets rejected. Trust but verify. Mostly verify.

For OAuth flows: validate the iss (issuer) claim to make sure the token came from your expected identity provider. Validate the aud (audience) claim to confirm the token was issued for your app. A valid token from your dev environment’s identity provider should never be accepted by production. This exact misconfiguration shows up in production at organizations that otherwise take security seriously. The dev key opening the production door.

JWT Algorithm Confusion: RS256 to HS256 DowngradeJWT Algorithm Confusion AttackNormal: Server uses RS256 (asymmetric)Server signs with private key. Verifies with public key. Attacker cannot forge.Attack: Attacker changes alg header to HS256 (symmetric)Signs the token using the PUBLIC key as the HMAC secretVulnerable server verifies:alg=HS256. Uses public key as HMAC secret. Signature matches. Token accepted.Attacker forges any JWT. Admin access granted.Fix: Allowlist algorithms. Never let the token header choose the verification method.The JWT header is attacker-controlled. Never trust it for algorithm selection.
Prerequisites
  1. JWT verification library hard-codes the expected algorithm (RS256 or ES256, never none)
  2. Token expiry (exp) is validated on every request, not assumed
  3. Issuer (iss) and audience (aud) claims are validated against expected values
  4. Tokens are stored in httpOnly cookies, not localStorage (XSS grabs localStorage tokens)
  5. JWKS endpoint is cached with a TTL, not fetched on every validation

What the Industry Gets Wrong About API Security

“WAFs protect APIs.” WAFs catch injection attacks and known payload patterns. They can’t see BOLA, mass assignment, or business logic abuse because these attacks use valid tokens, valid request structures, and valid HTTP methods. The request looks legitimate at every layer the WAF inspects. Perfect badge. Wrong room. The fence doesn’t help.

“API scanning catches vulnerabilities.” Scanners test against known patterns. BOLA needs business context no scanner has. An endpoint returning someone else’s invoice looks identical to one returning your own at the HTTP level. Only a test that knows which invoices belong to which users catches the flaw. The scanner checks if the lock works. Doesn’t check who has the key.

“Rate limiting prevents data exfiltration.” Rate limiting slows it down. An attacker throttled to 10 requests per second still downloads your entire user database. Just takes hours instead of minutes. Rate limiting buys time for anomaly detection. It is not authorization.

Our take Every API endpoint that returns a resource by ID needs an ownership check. Not at the gateway. In the query itself. WHERE account_id = :user_account_id is the single most effective API security control, and it’s the one most consistently missing. If your ORM doesn’t enforce tenant scoping by default, your authorization is opt-in. Opt-in authorization is no authorization. Opt-in seatbelts would have the same result.

API Inventory and Shadow APIs

The gap between official API documentation and what’s actually running in production is consistently wider than teams expect. Prototype endpoints from six months ago. Legacy versions nobody sunset. Internal tools exposed through the same gateway. Doors nobody put on the floor plan. All unlocked.

Passive traffic analysis against your OpenAPI spec reveals them. Collect gateway traffic logs over a 2-4 week window, diff against documented endpoints, and every undocumented path that received traffic is a shadow API needing immediate security review.

Shadow API discovery comparing gateway traffic logs against OpenAPI spec inventoryCollect 2-4 weeks of gateway traffic. Compare observed endpoints against documented OpenAPI spec. The diff reveals shadow APIs: undocumented endpoints like admin configs, internal health checks, and legacy exports that bypass security controls.Shadow API Discovery: What Your Spec Doesn't KnowGateway Traffic Logs2-4 week collection window/api/v1/users/api/v2/users/api/v1/admin/config/internal/healthOpenAPI Spec/api/v1/users, /api/v2/users2 documented endpointsDiff AnalysisTraffic vs SpecWhat's undocumented?3 gaps foundShadow APIs Found/api/v1/admin/configAdmin panel. No auth check./internal/healthLeaks version info externally./legacy/exportBulk data export. No rate limit.You can't secure endpoints you don't know exist.
Build sunset enforcement into the gateway: Sunset headers with a hard deadline, then 410 Gone after it passes. Every active API version is attack surface that needs patching and monitoring. A 12-18 month sunset window, enforced regardless of consumer migration status, keeps the inventory manageable. API integration engineering covers designing APIs that are secure by default from the start.

When to sunsetWhen to keep
Version replaced with breaking changesActive consumers still migrating (within sunset window)
Fewer than 5 active consumers remainingRegulatory requirement for backward compatibility
No traffic for 30+ daysThird-party integrations with contractual SLA
Known security vulnerabilities unfixable in old versionVersion has unique functionality not in successor

That account enumeration from the opening. 500,000 records in three hours. Now add authorization at the data layer. Schema validation blocking unexpected fields. Rate limiting throttling the iteration. Anomaly detection flagging the pattern. Fence, door, room locks, cameras, guards. No single control stops every attack. Layered together, the 12-line Python script hits a wall at every floor.

Your WAF Can't See Your Biggest API Vulnerability

BOLA exploits use valid tokens and valid request structures. Detection requires business context no scanner has. Defense-in-depth means schema validation at the gateway, object-level authorization in application code, and shadow API discovery that finds the endpoints your documentation forgot about.

Map Your API Attack Surface

Frequently Asked Questions

What is broken object-level authorization and why do standard scanners miss it?

+

BOLA happens when an API returns a resource to any authenticated user without checking ownership. GET /invoices/84721 validates the JWT but not whether the requester owns invoice 84721. Scanners miss it because detection needs business context no tool has. BOLA remains the single most exploited API vulnerability class year over year. Manual testing or business-aware test suites are the only reliable way to find it.

Should API security be enforced at the gateway or the service level?

+

Both, with distinct jobs. The gateway handles JWT validation, rate limiting, and schema validation against your OpenAPI spec. Object-level authorization must happen in the service itself because only the service knows the business rules. Relying on the gateway alone for authorization is one of the most common API security mistakes. Gateway plus service-level checks together cover nearly every category in the OWASP API Top 10.

What are the most dangerous JWT implementation vulnerabilities?

+

Algorithm confusion is the most critical: accepting ’none’ as a signing algorithm or using an RS256 public key as an HS256 HMAC secret lets attackers forge valid tokens. Also dangerous: not enforcing the ’exp’ claim, accepting unexpected ‘iss’ or ‘aud’ values, and storing JWTs in localStorage where XSS can grab them. Fix: hard-code the expected algorithm, validate every claim, and use httpOnly cookies for token storage.

What is a shadow API and how do you discover them?

+

Shadow APIs are production endpoints missing from your official inventory. Teams routinely find far more active endpoints than their documentation covers. Discover them by comparing gateway traffic logs over a 2-4 week window against your OpenAPI spec. Anything the gateway routes but the spec doesn’t document is a shadow API that needs immediate security review.

How should API versioning policy address security risk from old versions?

+

Every active API version is attack surface that needs patching and monitoring. Set a 12-18 month maximum between sunset announcement and enforcement, then return 410 Gone responses after the deadline regardless of consumer migration status. Hard sunset enforcement shrinks your active API attack surface sharply compared to leaving old versions running forever.