HIGH bola idorsailsbearer tokens

Bola Idor in Sails with Bearer Tokens

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

Broken Object Level Authorization (BOLA) occurs when an API lacks proper authorization checks such that one user can view or modify another user’s resources. In Sails.js, this commonly arises when controllers look up records by identifier (e.g., a numeric ID or UUID) and return them without verifying that the requesting user is entitled to access that object. When Bearer Tokens are used for authentication, the risk increases if token validation is decoupled from object-level authorization, or if the token’s associated user identity is not consistently enforced across endpoints.

Consider a Sails API that uses JWT Bearer Tokens where the payload includes a user ID (sub). An endpoint like GET /api/users/:id should ensure that the requesting user’s ID from the token matches the :param.id. If the controller simply does User.findOne(req.param('id')), an attacker who knows or guesses another user’s ID can read or act on that resource despite presenting a valid Bearer Token. The token proves identity but does not prove authorization for that specific object. Sails Waterline ORM does not automatically enforce ownership; developers must explicitly add checks. Missing checks are more likely when APIs are built quickly or when developers assume route parameters are trustworthy. This is a BOLA flaw: authorization is bypassed by manipulating the object identifier while authentication (Bearer Token) remains valid.

Additionally, indirect object references can expose BOLA in Sails when using associations. For example, a request to GET /api/accounts/:accountId/transactions/:txId might validate the Bearer Token and confirm the transaction belongs to the account, but if the developer does not also confirm the account belongs to the requesting user, an attacker can iterate over account IDs to access another user’s transactions. Inconsistent authorization across nested resources amplifies BOLA. The API might return 200 with sensitive data or allow DELETE/PUT/PATCH on objects the user should not touch. This violates the principle of least privilege and can lead to mass data exposure or tampering.

Bearer Tokens themselves do not cause BOLA, but they can obscure the issue if token introspection or decoding is relied upon for identity without reinforcing object-level checks. In Sails, if policies or middleware only verify the token’s signature and expiry, and controllers trust user-supplied IDs, the authorization boundary is weak. Attackers do not need to compromise the token; they simply manipulate identifiers. The combination of a flexible framework like Sails, common use of Bearer Tokens for APIs, and missing per-request authorization creates a practical path for BOLA. Proper remediation requires validating that the resource’s owner matches the token’s subject on every data access, using model policies or service-layer checks, and ensuring referential integrity across associations.

Bearer Tokens-Specific Remediation in Sails — concrete code fixes

Remediation centers on ensuring that for every object access, the controller confirms the requesting user (from the Bearer Token) owns or is authorized to interact with that object. Below are concrete, realistic Sails code examples that demonstrate secure patterns.

Example 1: Securing a user profile endpoint

Assume an endpoint GET /api/users/me that returns the current user’s profile using a Bearer Token containing sub. The token is verified by an auth middleware that attaches req.user (with id and sub). The controller should use req.user.id and avoid trusting req.param('id').

// api/controllers/UserController.js
async me(req, res) {
  // req.user is set by auth middleware from Bearer Token payload
  if (!req.user) {
    return res.unauthorized({ error: 'Unauthorized' });
  }

  // Do not use req.param('id'); use the identity from the token
  const userId = req.user.id;

  const user = await User.findOne(userId);
  if (!user) {
    return res.notFound({ error: 'User not found' });
  }

  return res.ok(user);
}

This ensures the ID used is derived from the token, not from the request, preventing ID swapping attacks.

Example 2: Securing a nested resource with ownership check

For endpoints like GET /api/accounts/:accountId/transactions/:txId, verify both the account belongs to the user and the transaction belongs to that account.

// api/controllers/TransactionController.js
async show(req, res) {
  const { accountId, txId } = req.params;

  // Verify Bearer Token user exists
  if (!req.user || !req.user.id) {
    return res.unauthorized({ error: 'Unauthorized' });
  }

  // Confirm the account belongs to the requesting user
  const account = await Account.findOne({
    id: accountId,
    userId: req.user.id
  });
  if (!account) {
    return res.forbidden({ error: 'Access denied to this account' });
  }

  // Confirm the transaction belongs to the verified account
  const transaction = await Transaction.findOne({
    id: txId,
    accountId: account.id
  });
  if (!transaction) {
    return res.notFound({ error: 'Transaction not found' });
  }

  return res.ok(transaction);
}

This double-check pattern prevents BOLA across associations by ensuring the requesting user owns the parent and the child record is linked to that parent.

Example 3: Policy-based enforcement using Sails policies

Define a policy that checks object ownership for sensitive routes. Policies run before controllers and can short-circuit unauthorized requests.

// api/policies/ensureOwnership.js
module.exports = async function ensureOwnership(req, res, next) {
  // Expecting req.user from auth and a model name and record id in route options
  const { model, id } = req.options;
  if (!req.user || !model || !id) {
    return res.forbidden({ error: 'Forbidden' });
  }

  const record = await model.findOne(id);
  if (!record) {
    return res.notFound({ error: 'Not found' });
  }

  // Assume records have a userId attribute linking to the token subject
  if (record.userId !== req.user.id) {
    return res.forbidden({ error: 'Access denied to this resource' });
  }

  return next();
};

Apply the policy in config/policies.js for routes that require ownership checks. This centralizes authorization logic and reduces the chance of missing checks in individual controllers.

Example 4: Creating resources with correct ownership

When creating child records, explicitly set the user reference from the token rather than trusting client input.

// api/controllers/TransactionController.js
async create(req, res) {
  if (!req.user) {
    return res.unauthorized({ error: 'Unauthorized' });
  }

  const { accountId, amount, currency } = req.body;

  // Ensure the provided accountId belongs to the user
  const account = await Account.findOne({
    id: accountId,
    userId: req.user.id
  });
  if (!account) {
    return res.forbidden({ error: 'Invalid account or access denied' });
  }

  const transaction = await Transaction.create({
    accountId: account.id,
    amount,
    currency,
    userId: req.user.id // explicitly set ownership
  }).fetch();

  return res.created(transaction);
}

By tying object ownership to the Bearer Token subject and validating on read, write, and associate operations, BOLA is effectively mitigated in Sails applications using Bearer Tokens.

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 Sails Waterline relate to BOLA when Bearer Tokens are used?
Waterline does not enforce ownership automatically. Developers must add explicit checks that the requesting user (from Bearer Token identity) matches the object’s owner fields; otherwise BOLA can occur.
Can relying only on Bearer Token validation prevent BOLA?
No. Token validation confirms identity but not authorization for a specific object. Always pair token checks with per-request ownership or role-based authorization on the resource.