Insecure Direct Object Reference in Express with Basic Auth
Insecure Direct Object Reference in Express with Basic Auth — how this specific combination creates or exposes the vulnerability
Insecure Direct Object Reference (BOLA/IDOR) occurs when an API exposes internal object references—such as database IDs, file paths, or sequential keys—and allows an attacker to manipulate those references to access unauthorized data. In Express, this commonly appears in routes like /users/:id or /api/invoices/:invoiceId where the identifier is taken directly from the request without confirming that the requesting user has the right to view or modify that specific object.
Combining Basic Auth with IDOR introduces a subtle but significant risk. Basic Auth sends a base64-encoded username:password string in the Authorization header; it does not inherently scope or partition data. A server may authenticate the user (valid credentials), but then fail to enforce authorization checks tied to that authenticated identity. For example, if the route uses the authenticated username for some logging but still allows req.params.id to reference any resource, an attacker can iterate over IDs and read or modify data belonging to other users. Because Basic Auth lacks built-in scopes or tenant boundaries, the burden falls entirely on the application to enforce ownership or role checks, and missing checks directly enable BOLA/IDOR.
Consider an Express route that retrieves a user’s profile by ID:
app.get('/api/users/:id', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
return res.status(401).send('Unauthorized');
}
const base64 = authHeader.split(' ')[1];
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
const [username, password] = decoded.split(':');
// Naive credential check (in real apps, use hashed passwords and constant-time compare)
if (!isValidUser(username, password)) {
return res.status(401).send('Unauthorized');
}
const userId = req.params.id;
const userData = db.getUserById(userId);
if (!userData) {
return res.status(404).send('Not found');
}
res.json(userData);
});
In this example, authentication is performed, but authorization is absent: userId from the URL is used directly without verifying that it belongs to the authenticated username. An attacker who knows another valid user’s ID can access that data, resulting in an IDOR. The same pattern applies to resources such as invoices, documents, or configurations where the identifier is user-controlled but not bound to the authenticated identity.
middleBrick scans detect this class of issue by correlating authentication findings (Basic Auth usage) with unvalidated object references in route parameters. It checks whether the application uses the authenticated subject (e.g., username or mapped tenant) to constrain data access and highlights missing ownership or scope checks as high-severity findings aligned with OWASP API Top 10 (2023) A01: Broken Object Level Authorization.
Basic Auth-Specific Remediation in Express — concrete code fixes
Remediation centers on ensuring that after authenticating a user via Basic Auth, every data access uses the authenticated subject to enforce ownership or tenant boundaries. Do not rely on obscurity or sequential IDs; instead, bind the resource to the authenticated identity and validate on each request.
1) Enforce ownership by comparing the authenticated username with the resource owner before returning data:
app.get('/api/users/:id', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
return res.status(401).send('Unauthorized');
}
const base64 = authHeader.split(' ')[1];
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
const [username, password] = decoded.split(':');
if (!isValidUser(username, password)) {
return res.status(401).send('Unauthorized');
}
const userId = req.params.id;
// Ensure the requested user ID matches the authenticated username
if (String(userId) !== username) {
return res.status(403).send('Forbidden: insufficient scope');
}
const userData = db.getUserById(userId);
if (!userData) {
return res.status(404).send('Not found');
}
res.json(userData);
});
2) For resources owned by a user but keyed by an internal numeric ID, first look up the resource, then verify ownership via the authenticated subject:
app.get('/api/invoices/:invoiceId', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
return res.status(401).send('Unauthorized');
}
const base64 = authHeader.split(' ')[1];
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
const [username] = decoded.split(':');
if (!isValidUser(username, '')) { // passwordless check for demo; use proper validation in prod
return res.status(401).send('Unauthorized');
}
const invoiceId = req.params.invoiceId;
const invoice = db.getInvoiceById(invoiceId);
if (!invoice) {
return res.status(404).send('Not found');
}
// Authorize: ensure the invoice belongs to the authenticated user
if (invoice.username !== username) {
return res.status(403).send('Forbidden: invoice does not belong to authenticated user');
}
res.json(invoice);
});
3) Use parameterized queries or an ORM that enforces tenant scoping to prevent ID manipulation at the data layer. Always treat user-supplied identifiers as untrusted and validate against the authenticated identity.
middleBrick’s OpenAPI/Swagger analysis can help identify routes with path parameters that lack corresponding security or scope constraints. By cross-referencing spec definitions with runtime checks, it highlights endpoints where object references are not bound to the authenticated subject, supporting remediation aligned with compliance frameworks such as OWASP API Top 10 and SOC2.
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 |