Overview
Broken Object Level Authorization (BOLA) flaws occur when an API reveals or allows actions on resources simply by guessing or enumerating object identifiers in the URL, without validating that the requesting user has access to that particular object. In a Node.js with Express backend, this often translates into endpoints that fetch a resource by ID and return it without verifying ownership, enabling an attacker to read others' data or perform unauthorized operations if the resource is not properly scoped.
Manifestations in Express include routes that take an object ID from req.params (for example /documents/:id, /orders/:orderId) and query the database directly for that resource, or using a user-provided owner field to filter data without forcing an ownership check. Many developers rely on authentication (who the user is) but forget to enforce authorization (whether the user is allowed to access the object). This class of flaw can persist in apps that expose user- or admin-only objects through generic endpoints.
Real-world impact ranges from privacy violations (exfiltrating personal documents or records) to business data leaks, regulatory exposure, and reputational harm. In multi-tenant SaaS, a single bug can grant cross-tenant access to documents, orders, or configuration settings. Attackers may attempt mass enumeration of IDs and combine with insufficient request filtering to harvest sensitive data.
Remediation strategy involves enforcing object-level authorization in every route: verify ownership at the server side, centralize authorization logic, and scope database queries by owner or access control lists. Use middleware that loads the resource and checks permissions before sending a response, and prefer DB queries that include owner constraints. Add tests that explicitly attempt to access resources owned by others to catch regressions.
Code Fix Example
Node.js (Express) API Security Remediation
// Vulnerable
const express = require('express');
const app = express();
const db = {
documents: [
{ id: '1', ownerId: 'user1', content: 'Secret A' },
{ id: '2', ownerId: 'user2', content: 'Secret B' }
]
};
// Mock auth middleware
app.use((req, res, next) => {
req.user = { id: 'user1' }; // simulate authenticated user
next();
});
app.get('/vulnerable/documents/:id', (req, res) => {
// Vulnerable: no ownership check
const doc = db.documents.find(d => d.id === req.params.id);
if (!doc) return res.status(404).send('Not found');
res.json(doc);
});
// Fixed: authorization check via ownership
function loadDocument(req, res, next) {
const doc = db.documents.find(d => d.id === req.params.id);
if (!doc) return res.status(404).send('Not found');
if (doc.ownerId !== req.user.id) return res.status(403).send('Forbidden');
req.doc = doc;
next();
}
app.get('/secure/documents/:id', loadDocument, (req, res) => {
res.json(req.doc);
});
app.listen(3000, () => console.log('Server listening on port 3000'));