The Reality of Modern API Vulnerabilities
Modern applications are built around web APIs. Cloud services, mobile apps, and frontends exchange JSON payloads across millions of endpoints every second. However, securing an API is fundamentally different from securing a traditional web page. Traditional web firewalls look for SQL injection strings and cross-site scripting tags in form fields. Modern API attacks do not look like traditional attacks. Attackers do not send broken syntax. Instead, they send perfectly valid JSON payloads and manipulate business logic parameters.
To highlight these risks, the Open Web Application Security Project published the OWASP API Security Top 10. In this standard, the most dangerous threat is not SQL injection or cross-site scripting. The number one threat is Broken Object Level Authorization (BOLA), followed by Broken Authentication (API2), Broken Object Property Level Authorization (BOPLA / Mass Assignment, API3), and Unrestricted Resource Consumption (API4).
Security cannot be added to an API as an afterthought with a simple gateway plugin. Genuine API protection requires defense-in-depth implemented directly within your backend architecture. In this comprehensive guide, we dissect the mechanics of BOLA, BOPLA, and resource exhaustion, explain why traditional defenses fail, and provide clean, production-grade architectural patterns implemented in pure Java 25.
BOLA (API1): Broken Object Level Authorization
Broken Object Level Authorization (formerly known as Insecure Direct Object References or IDOR) occurs when an API endpoint accepts an object identifier from the client and retrieves that object without verifying whether the authenticated user owns or is authorized to view that specific record. It is consistently rated as the single most critical vulnerability across all cloud APIs.

Consider a typical REST endpoint: GET /api/v1/invoices/{invoiceId}. An authenticated user, Alice, logs in and views her invoice: /api/v1/invoices/INV_1001. Alice inspects the network tab and notices the predictable ID format. She modifies the URL to /api/v1/invoices/INV_1002, which belongs to another customer, Bob. In a vulnerable backend, the code looks like this:
// VULNERABLE CONTROLLER CODE
@GetMapping("/invoices/{id}")
public Invoice getInvoice(@PathVariable("id") String id) {
// The user has a valid JWT, so the authentication filter passes.
// BUT the repository fetches by ID alone, with ZERO ownership check!
return invoiceRepository.findById(id)
.orElseThrow(() -> new NotFoundException());
}
Because the authentication filter only verified that Alice possessed a valid token, the request reaches the database query. The database returns Bob’s invoice, leaking confidential financial records. Attackers write simple scripts to iterate over millions of IDs, exfiltrating entire customer databases through public API routes.
Architectural Defenses Against BOLA
To eliminate BOLA, you must enforce authorization at the data query boundary rather than relying on URL inspection. There are three essential architectural layers required to prevent BOLA:
1. Contextual Security Boundary
Every business logic query must require an authenticated UserContext parameter containing the caller’s userId and tenantId. The database query must never fetch purely by primary key. Instead, the query must incorporate tenant and ownership scoping directly into the predicate:
SELECT * FROM invoices
WHERE id = :invoiceId
AND tenant_id = :authenticatedTenantId
AND (owner_id = :authenticatedUserId OR :hasTenantAdminRole = true);
By enforcing this rule, if Alice requests Bob’s invoice ID, the query returns zero rows. The application layer handles it as a standard 404 Not Found or 403 Forbidden, preventing cross-user and cross-tenant information leakage.
2. Cryptographically Random Object Identifiers
Never expose sequential auto-incrementing integers (like 1, 2, 3) in public API paths. Sequential IDs make it trivial for attackers to enumerate all records in your database. Use Universally Unique Identifiers (UUIDv7) or high-entropy NanoIDs. While random IDs do not replace authorization checks, they prevent bulk automated harvesting and enumeration.
BOPLA (API3): Broken Object Property Level Authorization
Broken Object Property Level Authorization (API3:2023) consolidates two previously separate vulnerabilities: Mass Assignment and Excessive Data Exposure. It occurs when an API endpoint allows clients to read or update internal object properties that they should not have access to.

The Danger of Mass Assignment
Many web frameworks provide automatic model binding. When a JSON payload arrives, the framework deserializes the JSON directly into your persistent domain entity or database model. For example, consider an endpoint to update user profiles:
PATCH /api/v1/users/profile
Content-Type: application/json
{
"email": "attacker@evil.com",
"role": "SYSTEM_ADMIN",
"accountBalance": 999999.00,
"isEmailVerified": true
}
If the backend binds this JSON directly to a User entity, the JSON deserializer sets the role to SYSTEM_ADMIN and the accountBalance to 999999.00. When the service saves the entity, the attacker escalates their privileges and grants themselves unlimited funds. This attack vector caused massive vulnerabilities in major platforms, including GitHub and Docker Hub.
Defeating Mass Assignment with Strict DTOs
The only reliable defense against mass assignment is strict Data Transfer Object (DTO) separation. You must never expose or deserialize persistent database entities in API endpoints. Every write operation must define an immutable request record that whitelists only the exact properties that clients are permitted to change:
// STRICT DTO RECORD
public record UpdateProfileRequest(
String email,
String displayName
) {
public UpdateProfileRequest {
Objects.requireNonNull(email, "Email must not be null");
displayName = displayName != null ? displayName.trim() : "";
}
}
Because UpdateProfileRequest contains only email and displayName, fields like role or balance do not exist on the type. If an attacker injects them into the JSON payload, an active schema inspection filter detects the unrecognized field and immediately aborts the request with an HTTP 400 or 403 error.
Resource Governance and Rate Limiting (API4)
APIs that do not restrict incoming request rates or query costs are vulnerable to denial-of-service (DoS) attacks and massive cloud billing spikes. OWASP API4:2023 addresses Unrestricted Resource Consumption. Attackers exploit this in three primary ways:

1. Deep Pagination Attacks
Consider an endpoint that returns financial transactions: GET /api/v1/transactions?limit=1000000&offset=5000000. If the server does not enforce an upper limit on the limit parameter, the database reads millions of records into memory. This causes high CPU spikes, exhaust connection pools, and triggers Out-Of-Memory (OOM) crashes. Always enforce strict server-side bounds:
int pageSize = Math.min(requestedPageSize, 50); // Hard maximum 50 rows
2. Token Bucket Rate Limiting
Protect public and authenticated endpoints with distributed rate limiting. Using the Token Bucket algorithm, clients receive a burst allowance and a sustained replenishment rate. Rate limits should be applied at multiple tiers:
- Per Client IP: Protects public unauthenticated endpoints (such as login and password reset) against brute-force attacks.
- Per Authenticated Tenant: Guarantees fair resource sharing across multi-tenant customers.
- Per API Key / User ID: Restricts high-cost reporting or export endpoints.
OWASP API Security Mitigation Matrix
The table below summarizes the core vulnerabilities in the OWASP API Security Top 10 along with their architectural defenses:
| OWASP API Threat | Exploit Mechanism | Architectural Defense Pattern | Verification Standard |
|---|---|---|---|
| API1: BOLA | Altering object IDs in URL path | Contextual tenant & ownership query scoping | Automated cross-tenant access tests |
| API2: Broken Auth | Credential stuffing, token replay | Short-lived tokens, refresh rotation, anti-replay | Sliding window nonce verification |
| API3: BOPLA | Mass assignment of sensitive fields | Strict immutable DTO records with field whitelisting | ArchUnit DTO boundary rules |
| API4: Resource Consumption | Deep pagination, unthrottled bursts | Token Bucket rate limiting, hard pagination caps | Load fuzzing & memory profiling |
| API5: BFLA | Normal users calling admin routes | Centralized method authorization policies | Role-based integration test suites |
| API6: Server-Side Request Forgery | Feeding internal metadata URLs to webhooks | Outbound egress proxy with strict DNS allowlist | Private IP subnet egress blocking |
| API7: Security Misconfiguration | Verbose stack traces, open CORS | Standardized error DTOs, strict CSP / CORS | Security header automated scans |
| API8: Lack of Protection from Automated Threats | Credential brute forcing, scraping | CAPTCHA, IP reputation, behavioral heuristics | Rate limiting threshold alerts |
Practical Java 25 Security Implementation
To demonstrate these architectural mitigations in practice, our companion workshop implements a clean, robust security core in Java 25. Let us review the primary components that enforce BOLA and BOPLA prevention.
First, we inspect BolaGuard, which evaluates ownership and tenant invariants before data access occurs:
package com.webcodein.workshop.owasp.engine;
import com.webcodein.workshop.owasp.model.AccountRecord;
import com.webcodein.workshop.owasp.model.SecurityDecision;
import com.webcodein.workshop.owasp.model.UserContext;
import java.util.Objects;
public class BolaGuard {
public SecurityDecision evaluateAccess(UserContext user, AccountRecord account) {
Objects.requireNonNull(user, "User context must not be null");
Objects.requireNonNull(account, "Account record must not be null");
// 1. Cross-Tenant Isolation Enforcement
if (!user.tenantId().equals(account.tenantId()) && !user.isSystemAdmin()) {
return SecurityDecision.deny(
"CROSS_TENANT_ACCESS_DENIED",
"User tenant " + user.tenantId() + " cannot access resource of tenant " + account.tenantId()
);
}
// 2. Object Ownership or Administrative Delegation Enforcement
boolean isOwner = user.userId().equals(account.ownerId());
boolean hasTenantAdmin = user.hasRole("ROLE_TENANT_ADMIN") && user.tenantId().equals(account.tenantId());
boolean hasSystemAdmin = user.isSystemAdmin();
if (isOwner || hasTenantAdmin || hasSystemAdmin) {
return SecurityDecision.allow();
}
return SecurityDecision.deny(
"BOLA_OBJECT_OWNERSHIP_VIOLATION",
"User " + user.userId() + " does not own account " + account.accountId()
);
}
}
Next, let us examine BoplaInspector, which analyzes incoming JSON payloads for forbidden property injection attempts before model binding:
package com.webcodein.workshop.owasp.engine;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.webcodein.workshop.owasp.model.SecurityDecision;
import com.webcodein.workshop.owasp.model.UpdateAccountRequest;
import java.util.Iterator;
import java.util.Set;
public class BoplaInspector {
private static final Set<String> ALLOWED_UPDATE_FIELDS = Set.of("email", "displayName");
private static final Set<String> SENSITIVE_INTERNAL_FIELDS = Set.of("balance", "tier", "accountId", "tenantId", "ownerId", "role");
private final ObjectMapper objectMapper = new ObjectMapper();
public SecurityDecision inspectRawJsonForMassAssignment(String rawJson) {
try {
JsonNode root = objectMapper.readTree(rawJson);
Iterator<String> fieldNames = root.fieldNames();
while (fieldNames.hasNext()) {
String field = fieldNames.next();
if (SENSITIVE_INTERNAL_FIELDS.contains(field)) {
return SecurityDecision.deny(
"BOPLA_MASS_ASSIGNMENT_DETECTED",
"Tampering with internal property '" + field + "' is forbidden"
);
}
if (!ALLOWED_UPDATE_FIELDS.contains(field)) {
return SecurityDecision.deny(
"UNRECOGNIZED_PROPERTY",
"Property '" + field + "' is not permitted in update requests"
);
}
}
return SecurityDecision.allow();
} catch (Exception e) {
return SecurityDecision.deny("MALFORMED_JSON", e.getMessage());
}
}
public UpdateAccountRequest parseStrictDto(String rawJson) throws Exception {
SecurityDecision decision = inspectRawJsonForMassAssignment(rawJson);
if (!decision.allowed()) {
throw new SecurityException("BOPLA Violation: " + decision.reason());
}
return objectMapper.readValue(rawJson, UpdateAccountRequest.class);
}
}
When combined inside AccountManagementService, any attempt by an unauthorized user to substitute an ID or inject an unapproved JSON attribute is stopped instantly before database mutations take place.
Real-World Enterprise Case Studies
Case Study 1: Healthcare Portal Eliminating BOLA Record Leakage
A regional healthcare network allowed patients to access medical test results via a mobile API. During an independent security assessment, auditors found that modifying the recordId path parameter returned medical lab reports belonging to other patients. The engineering team refactored the data access layer. They enforced mandatory ownership queries where the patient’s authenticated identity was linked directly to database queries. Following deployment, penetration testing verified zero unauthorized cross-patient data access across 4 million records.
Case Study 2: Fintech SaaS Preventing Mass Assignment Account Overwrite
A digital lending provider maintained an account settings API. A security researcher discovered that including "interestRateDiscount": 0.05 in the JSON payload of a profile update allowed borrowers to lower their loan interest rates arbitrarily. The platform replaced dynamic entity binding with immutable Java 25 request records. By compiling strict DTO boundaries, all extraneous JSON properties were rejected at the API gateway layer.
Case Study 3: Global E-Commerce Marketplace Hardening Against Scrapers
An international marketplace suffered severe database slowdowns during product launches because price scrapers requested 100,000 products per query via open pagination parameters. The engineering team deployed a multi-tier rate limiting architecture using Redis token buckets and capped maximum query limits to 50 items. Database CPU load dropped by 64%, preserving smooth checkout operations during peak shopping events.
Production Anti-Patterns and Pitfalls
Anti-Pattern 1: Relying on UI Hiding for Authorization
A dangerous assumption is believing that because an administrative button is hidden in your web UI, normal users cannot call the endpoint. Attackers inspect network calls and invoke backend API URLs directly via curl or Postman. Always enforce authorization policies on backend controllers and service layers, never in frontend view logic.
Anti-Pattern 2: Returning Entire Domain Entities in Responses
When an API endpoint returns an entire database entity (such as return userEntity;), the JSON serializer exports all object fields. This routinely leaks password hashes, internal foreign keys, and administrative flags to clients. Always transform domain entities into dedicated response DTOs that expose only the exact fields required by the client.
Anti-Pattern 3: Unbounded In-Memory Collections
Loading unpaged database collections into memory (such as userRepository.findAll()) is a major denial-of-service hazard. If a table grows to 500,000 records, a single request can trigger an out-of-memory error that crashes the application server. Always mandate cursor-based pagination or bounded queries for all collection endpoints.
Frequently Asked Questions
What is the difference between BOLA and BFLA in OWASP API Security?
BOLA (Broken Object Level Authorization) occurs when a user accesses specific object instances belonging to another user (e.g. Alice viewing Bob’s invoice). BFLA (Broken Function Level Authorization) occurs when a user accesses administrative functions or endpoints they do not have the role to invoke (e.g. a regular user calling DELETE /api/v1/users).
How do strict DTOs prevent Mass Assignment vulnerabilities?
Strict DTOs define only the exact fields that clients are authorized to mutate. By avoiding direct deserialization into persistent database entities, any unauthorized properties sent by an attacker (like role or balance) cannot be bound or written to the database.
Why are UUIDs recommended over auto-incrementing integer IDs?
Sequential integer IDs make it trivial for attackers to guess and iterate over all records in your database. High-entropy UUIDs (like UUIDv7) prevent automated ID harvesting and enumeration, although they must still be accompanied by server-side authorization checks.
What is the most effective algorithm for API rate limiting?
The Token Bucket algorithm is widely considered the most effective for web APIs. It allows legitimate traffic bursts up to bucket capacity while enforcing a smooth, sustained replenishment rate, preventing resource exhaustion during peak traffic.
Can an API Gateway alone protect against BOLA?
No. API Gateways can verify authentication tokens and enforce rate limits, but they typically lack business domain context about who owns which specific database record. BOLA defense must be enforced within the application and database query layer where ownership rules reside.
Companion Workshop Code
Inspect the complete runnable Java 25 workshop project demonstrating BOLA and BOPLA mitigations, schema inspection, and ArchUnit test governance on GitHub:
Official References and Standards
- OWASP API Security Top 10 (2023 Edition) – Official project documentation and threat catalog.
- OWASP REST Security Cheat Sheet – Authoritative API security guidelines.
- NIST SP 800-207: Zero Trust Architecture – Federal standards for identity and access boundaries.
- PASETO vs. JWT: Modern Token Security – Footgun-free identity authentication on learn.webcodein.com.
- HTTP/3 and QUIC Protocol Deep Dive – Resilient network transport and packet security.
- Distributed API Rate Limiting Architecture – Token bucket algorithms and traffic shaping.
- PostgreSQL Row-Level Security for Multi-Tenant Systems – Enforcing tenant isolation at the database engine.
- Zanzibar Distributed Access Control – Fine-grained relationship-based authorization patterns.
