HIGH bola idorexpressbearer tokens

Bola Idor in Express with Bearer Tokens

Bola Idor in Express with Bearer Tokens — how this combination creates or exposes the vulnerability

Broken Level of Authorization (BOLA) is a class of API vulnerability where one user can access or modify resources that belong to another user. In Express.js applications that rely on Bearer Tokens for authentication, BOLA often arises when authorization checks are incomplete or inconsistent, even though a valid token proves identity.

Consider a typical setup where an access token identifies a user (e.g., via a subject claim like sub) but the server routes requests such as GET /users/:id without verifying that the :id path parameter matches the subject in the token. An attacker with a stolen or guessed token can simply change the numeric ID in the URL to access another user’s data. This is a BOLA issue: the authentication (token validation) succeeds, but the authorization (resource ownership check) is missing or misapplied.

In Express, this can happen when route handlers directly trust user-supplied identifiers. For example, if your token payload includes sub: "user-123" and you have a route /api/users/:userId, failing to compare req.user.sub with req.params.userId means any authenticated user can iterate over IDs and read others’ profiles, settings, or sensitive records. Common patterns that exacerbate this include:

  • Using sequential IDs (1, 2, 3) that are easy to guess.
  • Exposing internal database keys in URLs without an access control layer.
  • Applying token validation middleware but skipping resource-level ownership checks.

BOLA also intersects with other checks in middleBrick’s 12 parallel scans. For instance, Property Authorization ensures that each field returned from the server is appropriate for the token’s scope, while Input Validation ensures that IDs and parameters conform to expected formats before being used in database queries. Together, these highlight that BOLA in Express with Bearer Tokens is not just about verifying the token, but about consistently enforcing that the authenticated subject is allowed to operate on the requested resource.

Real-world analogies include scenarios where an API endpoint like GET /api/invoices/:invoiceId does not check whether invoiceId belongs to the authenticated tenant or user. Even with a valid Bearer Token, missing tenant or user context checks enable horizontal privilege escalation across accounts.

Bearer Tokens-Specific Remediation in Express — concrete code fixes

To fix BOLA in Express when using Bearer Tokens, enforce that every resource request validates the relationship between the token’s subject and the requested resource identifier. Below are concrete, secure patterns and code examples.

1. Validate ownership in route handlers

Always compare the authenticated subject from the token with the resource identifier before proceeding. For example:

// middleware/authenticate.js (pseudo, e.g., using express-jwt or a custom verify)
function authenticate(req, res, next) {
  const auth = req.headers.authorization;
  if (!auth || !auth.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  const token = auth.slice(7);
  try {
    const payload = verifyToken(token); // your JWT verify function
    req.user = payload; // { sub: 'user-123', scope: 'read:users' }
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// routes/users.js
const express = require('express');
const router = express.Router();
const { authenticate } = require('./middleware/authenticate');

router.get('/users/:userId', authenticate, (req, res) => {
  const requestingUserId = req.user.sub; // from Bearer Token payload
  const targetUserId = req.params.userId;
  if (requestingUserId !== targetUserId) {
    return res.status(403).json({ error: 'Forbidden: cannot access other user resources' });
  }
  // proceed to fetch and return user data
  res.json({ id: targetUserId, name: 'Alice' });
});

module.exports = router;

2. Use centralized authorization helpers

Encapsulate the check to avoid repeating logic and reduce mistakes:

// middleware/ensureOwnership.js
function ensureOwnership(req, res, next) {
  const subject = req.user?.sub;
  const id = req.params.id || req.params.userId;
  if (!subject || subject !== id) {
    return res.status(403).json({ error: 'Forbidden: insufficient permissions' });
  }
  next();
}

// routes/profile.js
const express = require('express');
const router = express.Router();
const { authenticate } = require('./middleware/authenticate');
const ensureOwnership = require('./middleware/ensureOwnership');

router.get('/profile/:userId', authenticate, ensureOwnership, (req, res) => {
  // safe to proceed: subject matches userId
  res.json({ email: '[email protected]' });
});

module.exports = router;

3. Apply scope-based checks for different resources

For endpoints that involve collections (e.g., invoices), verify tenant or user context explicitly:

// routes/invoices.js
router.get('/tenants/:tenantId/invoices/:invoiceId', authenticate, (req, res) => {
  const tenantId = req.params.tenantId;
  const userId = req.user.sub; // assume token includes tenant association
  // Ensure user belongs to tenant before accessing invoice
  if (!userBelongsToTenant(userId, tenantId)) {
    return res.status(403).json({ error: 'Forbidden: user not in tenant' });
  }
  // fetch invoice for tenant
  res.json({ invoiceId: req.params.invoiceId, amount: 100 });
});

function userBelongsToTenant(userId, tenantId) {
  // check database or directory
  return true; // simplified
}

4. Enforce strict ID formats and canonical IDs

Use UUIDs or opaque identifiers instead of sequential integers to reduce guessability, and always normalize IDs before comparison:

const { v4: uuidv4 } = require('uuid');

app.post('/users', (req, res) => {
  const id = uuidv4(); // e.g., 'a1b2c3d4-...'
  // store user with this id
  res.status(201).json({ id });
});

// Then in route:
router.get('/users/:userId', authenticate, (req, res) => {
  const normalizedId = normalizeId(req.params.userId);
  if (req.user.sub !== normalizedId) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  // safe
});

5. Complement with middleware for input validation and property-level checks

Use express-validator or similar to ensure IDs are well-formed, and apply property authorization to limit returned fields based on scope:

const { body, param, validationResult } = require('express-validator');

router.get(
  '/users/:userId',
  authenticate,
  param('userId').isUUID(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    if (req.user.sub !== req.params.userId) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    // proceed safely
    res.json({ id: req.params.userId, name: 'Bob' });
  }
);

These patterns ensure that Bearer Token authentication is paired with strict ownership checks, reducing the risk of BOLA in Express APIs.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

Does using UUIDs instead of integers fully prevent BOLA in Express with Bearer Tokens?
Using UUIDs reduces predictability, but BOLA is prevented only when you consistently enforce that the authenticated subject (from the Bearer Token) matches the resource identifier in every request. UUIDs alone are not sufficient without proper ownership checks.
How does middleBrick handle BOLA checks in Express APIs with Bearer Tokens?
middleBrick runs parallel security checks including BOLA/IDOR and Property Authorization. It compares authentication context (e.g., subject in Bearer Token claims) with resource identifiers observed during scanning, and reports findings with remediation guidance when ownership checks are missing or inconsistent.