Bola Idor in Express with Basic Auth
Bola Idor in Express with Basic Auth — how this specific combination creates or exposes the vulnerability
Broken Level of Authorization (BOLA) is an API-specific form of IDOR that occurs when one user can access or modify another user's resources because the application fails to enforce ownership or tenant boundaries at the business logic layer. In Express, combining Basic Auth with BOLA typically means authentication succeeds (the server validates the username and password) but authorization does not. An attacker who obtains a valid credential for one account can then manipulate object IDs in requests to access or change data belonging to other users.
Consider an endpoint like GET /users/:userId/profile. If the server only checks that Basic Auth credentials were provided and maps the userId parameter directly to a database query without verifying that the authenticated identity matches that user, the endpoint is vulnerable. For example, a user with credentials alice:secret can change the URL to /users/bob/profile and, if no ownership check exists, potentially view or operate on Bob’s data. This becomes more impactful when IDs are predictable (sequential integers or UUIDs without access controls), and when authorization is implemented as route parameters or query strings rather than enforced against the authenticated principal.
With Basic Auth, credentials are sent on every request in the Authorization header as Basic base64(username:password). While this provides transport-layer identity, it does not inherently bind identity to authorization decisions. MiddleBrick’s checks include unauthenticated probing and runtime mapping of spec definitions to behavior; when an OpenAPI spec defines a path like /users/{userId} but does not require a scope or claim tying the authenticated subject to {userId}, the scan can highlight a BOLA risk. Attack patterns such as IDOR via direct object reference (Insecure Direct Object References) commonly map to OWASP API Top 10 A1: Broken Object Level Authorization. Real-world examples include accessing other tenants’ records in multi-tenant systems or viewing sensitive resources by iterating IDs, which can lead to data exposure and further privilege escalation when combined with other flaws.
In Express, BOLA with Basic Auth often surfaces through missing or incomplete middleware that compares the authenticated user (from req.user or a decoded token) to the resource owner derived from req.params or req.query. Without this check, even properly formatted Basic Auth credentials do not prevent horizontal privilege escalation. The scanner tests endpoints by attempting cross-user access using known or guessed identifiers, confirming whether the server enforces ownership rather than mere authentication.
Basic Auth-Specific Remediation in Express — concrete code fixes
To mitigate BOLA in Express when using Basic Auth, enforce that the authenticated identity matches the requested resource identifier on every request that accesses user-specific data. Below are concrete, working code examples that demonstrate secure patterns.
Example 1: Basic Auth middleware with ownership check
Use a dedicated authorization step after authentication to compare the authenticated user’s unique ID with the resource ID in the route parameters.
const express = require('express');
const basicAuth = require('express-basic-auth');
const app = express();
// In-memory users store (for illustration; use a secure store in production)
const users = {
alice: { password: 'alicepass', id: 'u-alice', name: 'Alice' },
bob: { password: 'bobpass', id: 'u-bob', name: 'Bob' }
};
app.get('/users/:userId/profile',
basicAuth({
users: users,
challenge: true
}),
(req, res, next) => {
// req.auth contains { user: 'alice', password: 'alicepass' }
const authenticatedUser = users[req.auth.user];
const targetUserId = req.params.userId;
if (!authenticatedUser || authenticatedUser.id !== targetUserId) {
return res.status(403).json({ error: 'Forbidden: insufficient permissions' });
}
res.json({ id: authenticatedUser.id, name: authenticatedUser.name });
}
);
app.listen(3000, () => console.log('Server running on port 3000'));
Example 2: Centralized authorization helper
For larger apps, encapsulate the check to keep route handlers clean and consistent.
function ensureOwnProfile(req, res, next) {
const authenticatedId = req.user ? req.user.id : null;
const targetId = req.params.userId;
if (!authenticatedId || authenticatedId !== targetId) {
return res.status(403).json({ error: 'Forbidden: cannot access this profile' });
}
return next();
}
app.get('/users/:userId/settings',
basicAuth({ /* users config */ }),
(req, res, next) => {
// Map req.auth to req.user for convenience
req.user = users[req.auth.user];
next();
},
ensureOwnProfile,
(req, res) => {
res.json({ settings: { theme: 'dark' } });
}
);
Additional recommendations
- Avoid exposing sequential or easily guessable IDs when possible; use opaque identifiers (random UUIDs) and map them server-side after authorization.
- Combine with HTTPS to protect Basic Auth credentials in transit.
- Log authorization failures for audit and anomaly detection, but do not expose sensitive details in error responses.
These patterns ensure that authentication (who you are) is separated from and enforced alongside authorization (what you are allowed to do), reducing the surface area for BOLA-style IDOR when Basic Auth is used.
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 |