HIGH insecure direct object referencefeathersjsapi keys

Insecure Direct Object Reference in Feathersjs with Api Keys

Insecure Direct Object Reference in Feathersjs with Api Keys — how this combination creates or exposes the vulnerability

In FeathersJS, an Insecure Direct Object Reference (IDOR) occurs when an API endpoint uses user-supplied input to directly access a resource (such as a row in a database or a file) without verifying that the requesting user is authorized to access that specific object. When API keys are used for authentication, developers may assume the key alone is sufficient to authorize any request to a given endpoint. This assumption can lead to an IDOR if the endpoint resolves a resource identifier (for example, a record ID from a service URL) and does not confirm that the resource is associated with the authenticated principal, even when an API key is presented.

Consider a Feathers service defined at /api/projects. A developer secures the service with an API key header, but the endpoint allows a request like GET /api/projects/123 where 123 is a project identifier taken directly from the URL. If the service implementation retrieves the project by ID without checking whether the authenticated API key’s owner has permission to view or modify that project, an IDOR exists. Attackers can enumerate predictable numeric IDs or guess UUIDs and access data belonging to other tenants or users. This risk is compounded when the API key is leaked or shared inadvertently, because the key alone does not enforce per-object authorization boundaries.

FeathersJS typically uses hooks to manage authentication and authorization. If the authentication hook attaches an API key’s associated identity to the params object (for example, params.account), but the service code does not use that identity to filter results (for example, by adding a query condition like query: { userId: params.account.userId }), the endpoint remains vulnerable. Even when using a multi-tenant schema, failing to scope queries by tenant ID derived from the API key leads to cross-tenant data exposure. The scanner category BOLA/IDOR in middleBrick specifically tests for these patterns by submitting requests with different resource identifiers while holding a constant API key, then checking whether data outside the authenticated subject’s scope is returned.

Real-world attack patterns aligned with this issue include accessing other users’ profiles, reading or modifying other customers’ orders, or viewing administrative resources by iterating over numeric IDs. Because FeathersJS can expose REST-like routes automatically from service definitions, developers might unintentionally expose endpoints that are not sufficiently scoped. The presence of an API key does not remove the need to validate authorization at the object level; it only establishes identity, not permission on a specific resource.

Api Keys-Specific Remediation in Feathersjs — concrete code fixes

To remediate IDOR when using API keys in FeathersJS, ensure that every data retrieval and mutation is scoped to the authenticated principal associated with the key. This requires modifying hooks to augment the query with ownership or tenant filters before the service executes the database operation. Below are concrete examples that demonstrate a secure approach.

First, configure an authentication hook that extracts the API key and maps it to an account identity. Then, in the service hook, enforce that queries include a filter on the account identifier. For a Feathers service using an external authentication strategy such as an API key header, you can implement it like this:

// src/hooks/authentication.js
const apiKeyAccountMap = new Map(); // In practice, use a secure lookup (e.g., DB)
apiKeyAccountMap.set('trusted-key-abc', { userId: 'user-1', tenantId: 'tenant-abc' });

module.exports = {
  before: async (context) => {
    const { headers } = context;
    const apiKey = headers['api-key'] || headers['x-api-key'];
    const account = apiKeyAccountMap.get(apiKey);
    if (!account) {
      throw new Error('Invalid API key');
    }
    // Attach account metadata to the request context used by services
    context.params.account = account;
    return context;
  }
};

Next, create or update your service hook to scope queries to the authenticated account. For a Feathers service using feathers-sequelize or feathers-mongoose, add a before find hook that injects the account filter:

// src/hooks/scoped-resources.js
module.exports = function scopedResources(options = {}) {
  return async context => {
    const { account } = context.params;
    if (!account) {
      throw new Error('Authentication required');
    }
    // For MongoDB with Mongoose, ensure queries are scoped by tenantId and userId
    if (context.params.sequelize || context.params.Model) {
      // Sequelize: add where clause to the query
      context.params.sequelize = context.params.sequelize || {};
      context.params.sequelize.where = context.params.sequelize.where || {};
      if (account.tenantId) {
        context.params.sequelize.where.tenantId = account.tenantId;
      }
      if (account.userId) {
        context.params.sequelize.where.userId = account.userId;
      }
    } else if (context.app) {
      // Generic adapter pattern: augment the query object
      context.params.query = context.params.query || {};
      if (account.tenantId) {
        context.params.query.tenantId = account.tenantId;
      }
      if (account.userId) {
        context.params.query.userId = account.userId;
      }
    }
    return context;
  };
};

Register these hooks in your service definition so that every request is constrained to the authenticated subject’s scope:

// src/services/projects/projects.service.js
const { Service } = require('feathers-sequelize');
const authentication = require('../../hooks/authentication');
const scopedResources = require('../../hooks/scoped-resources');

module.exports = function (app) {
  const options = {
    name: 'projects',
    Model: app.get('sequelizeProjectModel'),
    paginate: { default: 25, max: 50 }
  };

  app.use('/api/projects', new Service(options));
  const service = app.service('/api/projects');

  service.hooks({
    before: {
      all: [authentication, scopedResources()],
      find: [],
      get: [],
      create: [],
      update: [],
      patch: [],
      remove: []
    }
  });
};

In this setup, even if an attacker guesses or iterates over project IDs, the injected query filters ensure that records from other tenants or users are never returned. The API key establishes identity, while the hook enforces authorization at the object level. middleBrick’s CLI can validate this remediation by running middlebrick scan <url> and confirming that cross-object access is not disclosed.

Additionally, avoid exposing raw database IDs in URLs where possible; use opaque identifiers (e.g., UUIDs) and map them server-side to internal keys while still scoping by account. Combine API key validation with role-based access checks when operations require elevated privileges. Regularly rotate keys and monitor for anomalous patterns using the dashboard or alerts available in the Pro plan to detect possible credential misuse early.

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 API keys alone prevent IDOR in FeathersJS?
No. API keys establish identity but do not enforce object-level authorization. You must scope queries to the authenticated principal to prevent IDOR.
How can I verify my FeathersJS endpoints are protected against IDOR?
Use middleBrick’s CLI to scan your API: middlebrick scan <url>. Review the BOLA/IDOR findings and ensure hooks filter data by authenticated identity.