
OWASP Top 10 API: The One Risk Most Developers Completely Ignore
The OWASP Top 10 API Risk Nobody Fixes Properly
The OWASP Top 10 API Security Risks (2023 edition) lists ten categories. Most developers glance at the list, nod, and move on. But here's the thing: API1:2023 — Broken Object Level Authorization (BOLA) is responsible for a disproportionate number of real API breaches, and it's the one developers are worst at catching.
Why? Because no framework solves it for you. Authentication libraries handle login. Rate limiters handle abuse. But authorization at the object level? That's on you, in every single endpoint, every single time.
What BOLA Actually Looks Like
BOLA happens when your API lets an authenticated user access or modify another user's data simply by changing an ID in the request. It's embarrassingly simple to exploit.
In 2023, a BOLA vulnerability in a major fintech API let users download other users' tax documents by iterating through document IDs. Authenticated, valid tokens, zero permission checks on the resource itself.
Here's the vulnerable pattern — a Node.js Express endpoint:
// ❌ VULNERABLE: No ownership check
app.get('/api/invoices/:invoiceId', authenticate, async (req, res) => {
const invoice = await Invoice.findById(req.params.invoiceId);
if (!invoice) return res.status(404).json({ error: 'Not found' });
res.json(invoice);
});The user is authenticated. The invoice exists. But the code never checks whether this user owns this invoice. Any authenticated user can fetch any invoice by guessing or iterating IDs.
The Fix
// ✅ FIXED: Ownership check on every resource access
app.get('/api/invoices/:invoiceId', authenticate, async (req, res) => {
const invoice = await Invoice.findOne({
_id: req.params.invoiceId,
userId: req.user.id // Scoped to authenticated user
});
if (!invoice) return res.status(404).json({ error: 'Not found' });
res.json(invoice);
});The difference is one line. The query is scoped to the authenticated user. If the invoice doesn't belong to them, it returns 404 — not 403 (which would confirm the resource exists).
Why the Rest of the OWASP Top 10 API List Matters Less (For Now)
Here's the full 2023 list for context:
- API1 — Broken Object Level Authorization
- API2 — Broken Authentication
- API3 — Broken Object Property Level Authorization
- API4 — Unrestricted Resource Consumption
- API5 — Broken Function Level Authorization
- API6 — Unrestricted Access to Sensitive Business Flows
- API7 — Server Side Request Forgery
- API8 — Security Misconfiguration
- API9 — Improper Inventory Management
- API10 — Unsafe Consumption of APIs
API2 (Broken Authentication) gets the most attention because auth libraries and tutorials are everywhere. API8 (Security Misconfiguration) gets caught by basic scanners. But API1, API3, and API5 — the authorization triad — require business logic understanding that no generic tool fully automates.
This is why BOLA persists. It's not a configuration problem. It's a logic problem that lives in your application code.
Patterns That Prevent BOLA Across Your Entire API
Fixing individual endpoints isn't enough. You need a systemic approach:
- Default-deny query scoping: Build a data access layer that always filters by the authenticated user's context. Never call
findByIdon user-owned resources without scoping. - Middleware-level ownership checks: Create reusable middleware that loads the resource and verifies ownership before the handler runs.
- Use UUIDs, not sequential IDs: This doesn't fix BOLA, but it eliminates trivial enumeration. Treat it as defense in depth, not a solution.
- Write authorization tests explicitly: For every endpoint that accesses a resource, write a test where User A tries to access User B's data. If you don't have these tests, you have BOLA vulnerabilities. Period.
Automated scanners like PreBreach can flag endpoints that appear to lack authorization checks, but the ground truth always requires understanding your data model.
Your Action Items
Stop treating the OWASP Top 10 API list as a checklist to skim. Do these three things this week:
- Audit every endpoint that takes a resource ID as a parameter. Search your codebase for
findById,findByPk, or equivalent. If the query isn't scoped to the authenticated user, it's vulnerable. - Add "cross-user access" integration tests. Create two test users. For every resource endpoint, verify that User A cannot access User B's resources. Automate this in CI.
- Build a scoped data access layer. Stop relying on individual developers remembering to add ownership checks. Make the secure path the default path in your ORM or repository pattern.