Broken Object Level Authorization

Broken Object Level Authorization in Node.js/Express [GHSA-947f-4v7f-x2v8]

[Updated May 2026] Updated GHSA-947f-4v7f-x2v8

Overview

Broken Object Level Authorization (BOLA) is a widespread issue where an authenticated user can access, modify, or delete resources belonging to another user by manipulating object identifiers in API requests. In real-world Node.js/Express apps, this often manifests when an endpoint returns data by ID without verifying ownership, enabling data leakage and privilege escalation. Attackers can enumerate IDs such as /api/users/:id or /api/orders/:orderId and retrieve or alter data that should be restricted. The impact can be severe, including exposure of personal data, financial information, or the ability to perform actions on others' resources. In Express, BOLA typically arises when authorization is performed after data retrieval or when the client supplies a direct object reference that is trusted without cross-checking ownership. This pattern allows an authenticated but unauthorized user to walk across resource boundaries simply by changing identifiers in the request. Mitigations must enforce ownership or access control at the data access layer and route level, not only in business logic or after fetching the resource. The real-world risk includes data leakage, unauthorized edits or deletions, and cascading security consequences across services that share resources. A robust defense-in-depth strategy is required: verify ownership in every sensitive endpoint, scope DB queries to the current user, and centralize authorization logic to minimize gaps. No CVEs are linked in this guide, but the remediation pattern remains applicable across Node.js/Express ecosystems.

Code Fix Example

Node.js (Express) API Security Remediation
Vulnerable pattern (no ownership check):
const express = require('express');
const app = express();

// Mock authentication middleware
app.use((req, res, next) => {
  req.user = { id: 'u1', name: 'Alice' };
  next();
});

// In-memory data for demonstration
const orders = [
  { id: '1', ownerId: 'u1', total: 100 },
  { id: '2', ownerId: 'u2', total: 200 }
];

// Vulnerable: returns an order by id without checking ownership
app.get('/api/orders/:orderId', async (req, res) => {
  const order = orders.find(o => o.id === req.params.orderId);
  res.json(order);
});

// Fixed: verify ownership before returning the resource
app.get('/api/orders/:orderId', async (req, res) => {
  const order = orders.find(o => o.id === req.params.orderId);
  if (!order) return res.status(404).json({ error: 'Not found' });
  if (order.ownerId !== req.user.id) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  res.json(order);
});

// Start server (for completeness)
// app.listen(3000, () => console.log('Server running on port 3000'));

CVE References

Choose which optional cookies to allow. You can change this any time.