OWASP API Security Top 10: The Definitive Defense Guide for 2025
A deep dive into the most exploited API vulnerabilities and the architectural patterns needed to stop them.
APIGate Team
Engineering
API Security is Different
Standard web security (SQLi, XSS) is largely solved by modern frameworks. But **Logic Security**—how your API handles data access and business rules—is where the battles are won and lost today. Here are the top 5 vulnerabilities according to the OWASP API Security Project, with real-world defense patterns.
1. BOLA: Broken Object Level Authorization (The #1 Killer)
The Scenario: An attacker authenticates as User A (ID: 100). They change the URL to /api/messages/105 (User B's message). If the API returns the data, you have BOLA.
The Defense: Middleware Authorization. Never rely on "hidden" IDs. Every controller must check ownership.
// Middleware: ensureOwnership.ts
const ensureOwnsResource = async (req, res, next) => {
const resourceId = req.params.id;
const userId = req.user.id;
const resource = await db.findById(resourceId);
if (resource.ownerId !== userId) {
return res.status(403).json({ error: "Access Denied" });
}
next();
};
2. Broken User Authentication
Attackers don't break crypto; they break implementation.
Common Flaw: Allowing weak passwords or failing to validate JWT signatures properly (alg: none attack).
Fix: Use One-Way Keyed Hashes for identifiers and enforce MFA for sensitive actions.
3. Excessive Data Exposure
Scenario: SELECT * FROM users. You return the entire object to the frontend, including password_hash, social_security_number, and is_admin. You rely on the frontend to hide it.
Fix: Data Transfer Objects (DTOs). Explicitly map the response.
function toUserDTO(userModel) {
return {
id: userModel.uuid,
username: userModel.username,
avatar: userModel.avatar
// NEVER include pii or internal flags here
};
}
4. Lack of Resources & Rate Limiting
APIs are expensive. An attacker finds a "heavy" endpoint (PDF generation) and hits it 1000 times. Your server CPU spikes to 100%.
Fix: Implement Tiered Rate Limiting. Guest: 10 req/min. User: 100 req/min. Admin: 1000 req/min.
5. Broken Function Level Authorization
Scenario: A user sends DELETE /api/users/1. The backend checks "Is user logged in?" (Yes). It deletes the user. It forgot to check "Is user an Admin?".
Fix: RBAC Decorators.
@RequireRole('ADMIN') // This decorator runs BEFORE the function
async function deleteUser(req, res) {
await db.delete(req.params.id);
}