HIGH bola idorbearer tokens

Bola Idor with Bearer Tokens

How BOLA/IdOR Manifests in Bearer Tokens

Bearer tokens are a common authentication mechanism in APIs, but they create a unique vulnerability when combined with BOLA/IdOR (Broken Object Level Authorization / Insecure Direct Object References). The fundamental issue arises from how these tokens are implemented and validated.

When an API receives a Bearer token, it typically extracts user information from the token payload and uses that to authorize access to resources. The vulnerability occurs when the API fails to verify that the authenticated user actually owns or has permission to access the specific resource they're requesting.

Consider this common pattern:

const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const userId = decoded.sub;
const orderId = req.params.orderId;

// Vulnerable: No check that userId owns orderId
const order = await db.orders.findOne({ id: orderId });

An attacker with a valid Bearer token can exploit this by simply changing the resource identifier in the request. If they're authenticated as user 123 but know (or guess) that user 456 has order 789, they can request /orders/789 and receive the data because the API never validates ownership.

Real-world examples demonstrate this pattern. In 2022, a major e-commerce platform had a vulnerability where authenticated users could access any other user's order history by modifying the order ID in the URL. The API validated the Bearer token but never checked if the requester owned the specific order.

Another common manifestation involves array-based queries:

// Vulnerable: Returns all orders for any ID provided
const orders = await db.orders.find({ userId: decoded.sub, ids: { $in: req.body.orderIds } });

If an attacker sends orderIds: [123, 456, 789] where they only own 123, the API returns all three orders because it trusts the client-provided array without validating each ID.

Batch operations are particularly susceptible. APIs that accept arrays of resource IDs for bulk operations often fail to validate that the authenticated user owns all items in the array:

// Vulnerable batch endpoint
app.post('/users/:userId/documents/batch', async (req, res) => {
  const { documentIds } = req.body;
  const documents = await Document.find({ _id: { $in: documentIds } });
  res.json(documents);
});

An authenticated user can request documents belonging to other users simply by including their document IDs in the array.

Bearer Tokens-Specific Detection

Detecting BOLA/IdOR vulnerabilities in Bearer token implementations requires both manual code review and automated scanning. The key is identifying where resource access checks are missing after token validation.

Manual detection involves searching for patterns where:

  • Token validation occurs without subsequent ownership verification
  • Database queries use client-provided IDs without checking user ownership
  • Batch operations process arrays of IDs without per-item validation
  • Resource endpoints accept IDs that could belong to other users

Automated tools like middleBrick can scan Bearer token endpoints for these vulnerabilities. The scanner tests unauthenticated attack surfaces by attempting to access resources across different user contexts, identifying when APIs fail to enforce proper authorization boundaries.

middleBrick's Bearer token-specific detection includes:

Detection MethodDescriptionIndicator
Cross-user resource accessAttempts to access resources using different authenticated contextsSuccess when access should be denied
ID manipulation testingModifies resource identifiers in requestsUnexpected data exposure
Batch operation testingSends mixed ownership arrays in batch requestsPartial or full data leakage
Reference resolutionAnalyzes OpenAPI specs for missing authorization checksSchema-defined but unvalidated endpoints

During scanning, middleBrick identifies endpoints that accept resource identifiers without proper authorization checks. For example, if an API has an endpoint like /users/{userId}/orders/{orderId}, the scanner tests whether changing userId or orderId allows access to other users' data.

Code analysis can also reveal vulnerabilities. Search for patterns like:

// Red flag: No ownership check
const resource = await Resource.findById(req.params.id);

Or:

// Red flag: Trusting client-provided array
const resources = await Resource.find({ id: { $in: req.body.resourceIds } });

Effective detection requires understanding the business logic and data ownership models specific to your application. What constitutes a valid access pattern versus an attack varies by context.

Bearer Tokens-Specific Remediation

Remediating BOLA/IdOR vulnerabilities in Bearer token implementations requires adding proper ownership validation after token authentication. The core principle is always verify that the authenticated user has rights to the specific resource they're accessing.

Here's a secure pattern for single resource access:

app.get('/orders/:orderId', async (req, res) => {
  const token = req.headers.authorization.split(' ')[1];
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  
  const order = await db.orders.findOne({
    _id: req.params.orderId,
    userId: decoded.sub // Ensure user owns this order
  });
  
  if (!order) {
    return res.status(404).json({ error: 'Order not found or unauthorized' });
  }
  
  res.json(order);
});

The critical addition is the userId: decoded.sub condition, which ensures the order belongs to the authenticated user.

For batch operations, validate each item individually:

app.post('/batch-orders', async (req, res) => {
  const token = req.headers.authorization.split(' ')[1];
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  
  const { orderIds } = req.body;
  
  // Find orders that belong to this user AND are in the requested list
  const orders = await db.orders.find({
    _id: { $in: orderIds },
    userId: decoded.sub
  });
  
  // Verify all requested orders were found
  if (orders.length !== orderIds.length) {
    return res.status(403).json({ error: 'Unauthorized access to some orders' });
  }
  
  res.json(orders);
});

This pattern ensures that only orders belonging to the authenticated user are returned, and if any requested order doesn't belong to them, the entire operation fails.

For more complex authorization scenarios, implement a dedicated authorization service:

class AuthorizationService {
  static async canAccessOrder(userId, orderId) {
    const order = await db.orders.findOne({ _id: orderId, userId });
    return !!order;
  }
  
  static async canAccessDocument(userId, documentId, requiredRole = null) {
    const document = await db.documents.findOne({ _id: documentId });
    if (!document) return false;
    
    // Check ownership
    if (document.ownerId !== userId) return false;
    
    // Check role-based access if required
    if (requiredRole) {
      const user = await db.users.findOne({ _id: userId });
      return user.roles.includes(requiredRole);
    }
    
    return true;
  }
}

Then use it consistently across your API:

app.get('/documents/:documentId', async (req, res) => {
  const token = req.headers.authorization.split(' ')[1];
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  
  const canAccess = await AuthorizationService.canAccessDocument(
    decoded.sub, 
    req.params.documentId
  );
  
  if (!canAccess) {
    return res.status(403).json({ error: 'Access denied' });
  }
  
  const document = await db.documents.findOne({ _id: req.params.documentId });
  res.json(document);
});

Middleware can centralize authorization checks:

function requireOwnership(resourceType, resourceIdParam) {
  return async (req, res, next) => {
    const token = req.headers.authorization.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    let resource;
    switch (resourceType) {
      case 'order':
        resource = await db.orders.findOne({ 
          _id: req.params[resourceIdParam], 
          userId: decoded.sub 
        });
        break;
      case 'document':
        resource = await db.documents.findOne({ 
          _id: req.params[resourceIdParam], 
          ownerId: decoded.sub 
        });
        break;
    }
    
    if (!resource) {
      return res.status(403).json({ error: 'Access denied' });
    }
    
    req.resource = resource;
    next();
  };
}

// Usage
app.get('/orders/:orderId', 
  requireOwnership('order', 'orderId'), 
  (req, res) => {
    res.json(req.resource);
  }
);

The key remediation principle: never trust client-provided identifiers. Always verify ownership through server-side checks after Bearer token validation.

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

How does middleBrick detect BOLA/IdOR vulnerabilities in Bearer token APIs?
middleBrick scans unauthenticated endpoints and tests for resource access across different user contexts. It attempts to access resources using manipulated IDs and batch operations to identify when APIs fail to enforce proper ownership boundaries. The scanner analyzes OpenAPI specs to identify endpoints that accept resource identifiers without corresponding authorization checks, then actively tests these endpoints to confirm vulnerabilities.
What's the difference between authentication and authorization in Bearer token security?
Authentication verifies who you are (via the Bearer token), while authorization determines what you're allowed to do. A Bearer token might successfully authenticate a user, but if the API doesn't check whether that user owns or has permission to access a specific resource, it creates a BOLA/IdOR vulnerability. The token proves identity, but the API must still verify resource-level permissions.