Insecure Direct Object Reference in Express with Api Keys
Insecure Direct Object Reference in Express with Api Keys — how this specific combination creates or exposes the vulnerability
Insecure Direct Object Reference (BOLA/IDOR) in an Express API occurs when an endpoint uses user-supplied identifiers (such as a resource ID or username) to locate a server-side object without verifying that the authenticated subject has permission to access that specific instance. When API keys are used for authentication but authorization is missing or incomplete, the key identifies the client but not the scoped access to individual resources, allowing attackers to iterate through predictable identifiers and access other users’ data.
Consider an Express route that retrieves a user profile by ID without checking ownership:
// Risk: IDOR with API key authentication only
app.get('/api/users/:id', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) return res.status(401).send('Missing API key');
// validateApiKey(apiKey) might check key existence and scope, but does not ensure key owner can only access their own user record
db.getUserById(req.params.id).then(user => {
if (!user) return res.status(404).send('Not found');
res.json(user);
}).catch(() => res.status(500).send('Error'));
});
Here, the API key proves the caller is a known client, but it does not enforce that the client can only access the user record associated with that key. An attacker who knows or guesses another numeric ID can request /api/users/123 and obtain data they should not see. This is a classic BOLA/IDOR: direct object reference because the parameter id directly maps to a database row, and insecure because authorization is missing.
Another common pattern is exposing references that should be indirect, such as filenames or internal pointers, where the key does not restrict traversal. For example:
// Risk: IDOR via predictable file references with API key
app.get('/api/reports/:reportName', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) return res.status(401).send('Missing API key');
// No check that the report belongs to the API key owner
res.sendFile(`/data/reports/${req.params.reportName}`);
});
If reportName is user-controlled and not scoped to the key owner, an attacker can enumerate reports across other users. Because the scan category BOLA/IDOR runs in parallel with Authentication and checks for missing ownership validation, middleBrick would flag this as a high-severity finding with remediation guidance to enforce instance-level authorization.
In summary, combining Express routes with API key authentication creates risk when object references are not bound to the subject’s permissions. The key identifies the client, but without additional checks on the resource identifier, direct object references remain insecure.
Api Keys-Specific Remediation in Express — concrete code fixes
To fix BOLA/IDOR while keeping API keys as the authentication primitive, enforce that each operation validates ownership or scope relative to the key. Maintain a mapping between the API key and the allowed resource identifiers (e.g., tenant ID, user ID), and assert that the requested resource belongs to that scope.
Example remediation that ties the API key to a user ID and ensures users only access their own profile:
// Secure: enforce ownership based on API key mapping
const apiKeyToUserId = new Map(); // In practice, load from a secure store
app.get('/api/users/:id', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) return res.status(401).send('Missing API key');
const allowedUserId = apiKeyToUserId.get(apiKey);
if (allowedUserId == null) return res.status(403).send('Invalid API key');
if (req.params.id !== allowedUserId) {
return res.status(403).send('Forbidden: you can only access your own record');
}
db.getUserById(req.params.id).then(user => {
if (!user) return res.status(404).send('Not found');
res.json(user);
}).catch(() => res.status(500).send('Error'));
});
This ensures the API key maps to a subject, and the subject must match the object identifier in the request. For collections, scope the query to the subject instead of trusting the client-supplied identifier:
// Secure: scope database query to the subject derived from API key
app.get('/api/reports/:reportName', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) return res.status(401).send('Missing API key');
const ownerId = apiKeyToUserId.get(apiKey);
if (ownerId == null) return res.status(403).send('Invalid API key');
// Only fetch reports where owner_id matches the key owner
db.getReportByNameAndOwner(req.params.reportName, ownerId).then(report => {
if (!report) return res.status(404).send('Not found or access denied');
res.json(report);
}).catch(() => res.status(500).send('Error'));
});
Additional recommendations include rotating API keys, scoping keys to roles or tenants, and continuous monitoring of access patterns. middleBrick’s Pro plan supports continuous monitoring and can integrate into your CI/CD pipeline via the GitHub Action to fail builds if risk thresholds are exceeded; the MCP Server lets you run scans directly from your AI coding assistant within the IDE.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |