Privilege Escalation in Express with Bearer Tokens
Privilege Escalation in Express with Bearer Tokens — how this specific combination creates or exposes the vulnerability
In Express applications, privilege escalation via Bearer tokens often occurs when authorization checks are incomplete or applied inconsistently across routes. A common pattern is to verify the presence of a token (e.g., via Authorization: Bearer <token>) but not validate scope, role, or tenant context before processing a request.
Consider an endpoint intended for administrators:
// BAD: No role or scope check
app.delete('/users/:id', authenticateToken, (req, res) => {
const userId = req.params.id;
deleteUser(userId);
res.sendStatus(204);
});
If the token validation middleware only confirms the token is well-formed (e.g., JWT signature and expiry) but does not enforce role-based access control (RBAC), any authenticated user could send a DELETE request to /users/123 and delete another account. This is a BOLA/IDOR-like escalation where the subject (user ID) is manipulated horizontally or vertically without authorization checks.
Another scenario involves misconfigured middleware ordering. For example, a route that should require admin privileges might be placed before or after a public route sharing a similar path prefix, allowing an unauthenticated or low-privilege caller to reach the handler under certain conditions. Inconsistent use of next() or early returns can also bypass intended authorization logic.
Additionally, tokens may contain roles or scopes (e.g., scope=read:users write:users) that are not validated on each request. If the server trusts the token’s claims without re-checking per-request authorization, an attacker who obtains a token with broader scopes can exploit endpoints that assume those scopes are enforced server-side.
These issues map to OWASP API Top 10 controls, particularly Broken Object Level Authorization (BOLA). Because the attack surface is unauthenticated in black-box scans, middleBrick runs checks that look for missing authorization on privileged operations and tests whether changing identifiers (such as user IDs) allows unauthorized actions, which is characteristic of BOLA/IDOR.
To detect such issues, scans may submit requests with different identifiers and tokens to see whether the server enforces boundaries. Findings include missing role validation, weak object ownership checks, and over-privileged token acceptance.
Bearer Tokens-Specific Remediation in Express — concrete code fixes
Secure Express APIs by combining robust token validation with explicit per-request authorization. Below are concrete, working examples that demonstrate correct patterns.
1. Token validation with role/scope extraction
Use a middleware that verifies the Bearer token and attaches a normalized user object to req, including roles and scopes:
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.JWT_PUBLIC_KEY, (err, decoded) => {
if (err) return res.sendStatus(403);
req.user = {
id: decoded.sub,
roles: decoded.roles || [],
scopes: decoded.scope ? decoded.scope.split(' ') : []
};
next();
});
}
2. Role-based authorization middleware
Create a reusable check that asserts required roles or scopes:
function requireRole(role) {
return (req, res, next) => {
if (!req.user.roles.includes(role)) {
return res.status(403).json({ error: 'insufficient_scope', message: 'Missing required role' });
}
next();
};
}
function requireScope(scope) {
return (req, res, next) => {
if (!req.user.scopes.includes(scope)) {
return res.status(403).json({ error: 'insufficient_scope', message: 'Missing required scope' });
}
next();
};
}
3. Applying checks to privileged endpoints
For an admin-only user deletion endpoint, combine authentication and role checks:
app.delete('/users/:id', authenticateToken, requireRole('admin'), (req, res) => {
const userId = req.params.id;
// Ensure req.user.id is not used to infer permissions; rely on roles/scopes
deleteUserById(userId);
res.sendStatus(204);
});
For endpoints where users manage their own data, enforce ownership in addition to authentication:
app.get('/users/:id/profile', authenticateToken, (req, res) => {
const requestedId = req.params.id;
const userId = req.user.id;
if (requestedId !== userId) {
return res.status(403).json({ error: 'forbidden', message: 'Cannot access other user profiles' });
}
const profile = getUserProfile(userId);
res.json(profile);
});
4. Scoped token usage for least privilege
When issuing tokens, include minimal scopes. For example, a token for reading user profiles might carry scope=read:users, while an admin token could have scope=read:users write:users delete:users. The server must validate scopes on each request as shown above.
These remediation steps align with OWASP API Top 10 controls and help prevent BOLA/IDOR and privilege escalation. Because middleBrick tests authorization boundaries and token handling, applying these patterns reduces findings related to authentication and authorization.