Insecure Direct Object Reference in Express
How Insecure Direct Object Reference Manifests in Express
Insecure Direct Object Reference (BOLA/IDOR) in Express occurs when an endpoint uses user-supplied identifiers (e.g., userId, documentId) to access a resource without verifying that the requesting subject has permission to access that specific instance. Attackers manipulate these identifiers to enumerate or act on other users’ data. Common Express code paths include route parameters, query strings, and body fields that map directly to database keys without an authorization guard.
Attack patterns specific to Express include:
- Path-based IDOR: A route like
GET /users/:userId/profilereturns profile data solely based on the parameter. If the handler does not confirm the authenticated user owns thatuserId, an attacker can increment or guess IDs to view other profiles. - Query-driven references: Using a query parameter such as
?document_id=123to fetch a document without checking that the authenticated user’s permissions include that document. - Associative references in nested resources: For example,
GET /organizations/:orgId/members/:memberIdwhereorgIdandmemberIdare used directly to query the database without validating that the authenticated user belongs to the specified organization and is allowed to view that member.
Express-specific code that is vulnerable typically looks like this:
// Vulnerable Express route: IDOR via userId parameter
app.get('/api/users/:userId', async (req, res) => {
const user = await db('users').where({ id: req.params.userId }).first();
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user); // No check that req.user.id === req.params.userId
});
An attacker can request /api/users/123 while authenticated as user 456 and receive user 123’s data if the server does not enforce ownership or role-based access control. Another common pattern is using a non-sequential identifier (e.g., a UUID) where the developer assumes obscurity prevents enumeration; without authorization checks, direct object references remain exploitable.
In more complex scenarios, IDOR intersects with business logic such as billing or administrative actions. For instance, an endpoint that applies a coupon or updates an email might trust an orderId parameter without verifying that the order belongs to the authenticated user or that the user has the required privileges. Because Express does not enforce authorization by default, these checks must be implemented explicitly in application code or via an authorization library.
Express-Specific Detection
Detecting IDOR in Express requires a combination of code review, runtime testing, and schema-aware scanning. Look for routes that accept identifiers referencing domain objects (users, documents, payments) and then inspect whether the handler enforces subject-to-object relationships. Indicators include missing ownership checks, direct use of req.params, req.query, or req.body values in database queries, and permissive route definitions that expose internal keys.
middleBrick scans API definitions and runtime behavior to surface IDOR risks across the unauthenticated attack surface. By analyzing OpenAPI/Swagger specs (2.0, 3.0, 3.1) with full $ref resolution and correlating definitions with observed endpoints, it highlights endpoints where identifiers are accepted without clear authorization constraints. The scanner runs 12 security checks in parallel, including BOLA/IDOR, to produce a risk score and prioritized findings with severity and remediation guidance. This makes it practical to detect IDOR patterns across large Express codebases without manual line-by-line review.
To detect IDOR during development, you can instrument tests that assert ownership checks. For example, an authenticated request to another user’s resource should return 403 or 404 rather than 200. Combine this with spec validation to ensure that security schemes and scopes are properly declared for endpoints that manipulate sensitive resources.
Express-Specific Remediation
Remediation centers on enforcing that the authenticated subject is authorized to access the referenced object. Use Express-native patterns and well-maintained libraries to centralize authorization logic rather than scattering checks across route handlers.
A robust approach is to resolve the object, then verify that the object’s ownership or access policy aligns with the authenticated subject. Below is a secure Express example that demonstrates this pattern using a lightweight data access layer and async handlers:
// Secure Express route: verify ownership before returning user data
async function requireUserOwnership(req, res, next) {
const userId = req.params.userId;
const user = await db('users').where({ id: userId }).first();
if (!user) return res.status(404).json({ error: 'Not found' });
if (String(user.id) !== String(req.user.id)) {
return res.status(403).json({ error: 'Forbidden' });
}
res.locals.user = user;
next();
}
app.get('/api/users/:userId/profile', requireUserOwnership, (req, res) => {
res.json(res.locals.user);
});
For more complex scenarios, consider an authorization library that supports roles and policies, but at minimum enforce object ownership for user-specific resources. When working with organizations or teams, validate group membership before allowing access to team-specific resources:
// Secure nested resource with org membership check
async function requireOrgMember(req, res, next) {
const { orgId, memberId } = req.params;
const membership = await db('memberships')
.where({ org_id: orgId, user_id: req.user.id })
.first();
if (!membership) return res.status(403).json({ error: 'Forbidden' });
const member = await db('users').where({ id: memberId }).first();
if (!member) return res.status(404).json({ error: 'Not found' });
res.locals.member = member;
next();
}
app.get('/organizations/:orgId/members/:memberId', requireOrgMember, (req, res) => {
res.json(res.locals.member);
});
Additional best practices for Express include:
- Centralize data access to ensure consistent ownership resolution.
- Use UUIDs to mitigate predictable ID enumeration, but remember that obscurity is not authorization.
- Apply the principle of least privilege: scopes and roles should limit what each token or session can do.
- Leverage middleware to keep authorization logic reusable and testable.
In CI/CD, the Pro plan’s GitHub Action can add API security checks and fail builds if risk scores exceed your threshold, helping catch IDOR regressions before deployment. The MCP Server allows you to scan APIs directly from your IDE, integrating security checks into the developer workflow without disrupting productivity.
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 |