HIGH broken access controlfeathersjsmongodb

Broken Access Control in Feathersjs with Mongodb

Broken Access Control in Feathersjs with Mongodb — how this specific combination creates or exposes the vulnerability

Broken Access Control in a Feathersjs service backed by Mongodb often arises when authorization checks are absent or misapplied at the service handler level. Feathersjs is service-oriented and does not enforce authorization by default; if you define a service without adding custom hooks or method-level guards, any authenticated (or sometimes unauthenticated) user can invoke create, get, update, or remove operations. When combined with Mongodb as the backing store, the risks compound because query filters constructed from user input may inadvertently expose records if ownership or role constraints are not enforced.

Consider a typical Feathersjs service for user documents where the Mongodb collection stores a userId field to indicate ownership. If the service allows querying like { userId: { $in: userIds } } but does not validate that the requesting user is included in userIds, an attacker can manipulate parameters (e.g., omitting userId or injecting other values) to access or enumerate other users' data. This maps to OWASP API Top 10 A1:2023 Broken Object Level Authorization (BOLA)/Insecure Direct Object References (IDOR). Because Feathersjs can expose REST-like routes and GraphQL-like patterns, an attacker may probe endpoints using unauthenticated or low-privilege sessions to test whether valid resource identifiers return data they should not.

Moreover, Feathersjs hooks that modify queries must be carefully designed. A hook that appends a filter like { organizationId: orgId } based on a token claim can be bypassed if the hook does not consistently apply to all operations or if runtime parameters override the filter. For example, an update call with query.$set or an improperly constrained patch may allow altering fields that should be immutable for certain roles. Privilege escalation may occur when higher-privilege operations such as remove or field updates are permitted for roles that should only read. The use of Mongodb update operators like $set, $inc, or positional operators can inadvertently expose modification capabilities if the service does not validate which fields a subject is allowed to change.

Another vector involves property-level authorization. If a Feathersjs service returns full Mongodb documents containing sensitive fields such as passwordHash, role, or internal identifiers, and does not explicitly project or redact these fields based on user roles, information disclosure occurs. This is especially risky when the service relies on Mongodb's default retrieval behavior without a schema layer that enforces field visibility. Insecure direct object references can also manifest when users reference predictable _id values and the service returns them without verifying that the authenticated subject has the right to view or modify that specific document.

To detect these issues, scanning tools evaluate whether services implement consistent authorization at the endpoint level, validate ownership within query filters, and apply strict field-level permissions. They also check whether hooks uniformly enforce constraints across all CRUD methods and whether responses inadvertently expose sensitive attributes. Without these controls, the Feathersjs + Mongodb stack remains susceptible to unauthorized read, update, and delete actions that violate the principle of least privilege.

Mongodb-Specific Remediation in Feathersjs — concrete code fixes

Remediation centers on enforcing ownership and role checks within Feathersjs hooks and ensuring Mongodb queries are constrained by the subject's permissions. Below are concrete, working code examples that demonstrate secure patterns.

1. Enforce userId ownership in a before hook

Use a before hook to inject the authenticated user's ID into the query, preventing users from requesting other users' data.

const { authenticate } = require('@feathersjs/authentication').hooks;

app.service('documents').hooks({
  before: {
    all: [authenticate('jwt')],
    find: [context => {
      // context.params.user contains the decoded JWT payload
      const { user } = context.params;
      // Ensure every query for documents includes the userId filter
      context.params.query = {
        ...context.params.query,
        userId: user.id
      };
      return context;
    }],
    create: [context => {
      const { user } = context.params;
      // Assign ownership on creation
      context.data.userId = user.id;
      return context;
    }],
    update: [context => {
      // Forbid updates that attempt to change userId
      if (context.data.userId && context.data.userId !== context.params.user.id) {
        throw new Error('Forbidden: Cannot change ownership');
      }
      // Ensure patch operations preserve original userId
      context.params.query = { userId: context.params.user.id };
      return context;
    }],
    patch: [context => {
      // Constrain patch to the authenticated user's document
      context.params.query = { userId: context.params.user.id };
      return context;
    }],
    remove: [context => {
      context.params.query = { userId: context.params.user.id };
      return context;
    }]
  }
});

2. Apply role-based field filtering in after hooks

Use an after hook to remove sensitive fields from the returned Mongodb documents based on roles.

app.service('users').hooks({
  after: {
    all: [context => {
      const { user } = context.params;
      // If not an admin, remove passwordHash and role from each returned document
      if (!user.roles.includes('admin')) {
        context.result.data = context.result.data.map(doc => {
          const { passwordHash, role, internalNotes, ...safeDoc } = doc;
          return safeDoc;
        });
      }
      return context;
    }]
  }
});

3. Validate and sanitize query parameters to prevent injection

Ensure incoming query filters cannot override security constraints by normalizing them before they reach Mongodb.

app.service('items').hooks({
  before: {
    find: [context => {
      const baseQuery = { deleted: { $ne: true } };
      // If the client provides a query, merge safely without allowing $where or $eval
      const clientQuery = context.params.query || {};
      // Remove potentially dangerous operators
      const unsafeOps = ['$where', '$eval', '$script'];
      unsafeOps.forEach(op => { delete clientQuery[op]; });
      context.params.query = { $and: [baseQuery, clientQuery] };
      return context;
    }]
  }
});

4. Use projection to limit returned fields

Explicitly define which fields are allowed for each operation to reduce exposure.

app.service('secrets').hooks({
  before: {
    find: [context => {
      // Only return permitted fields for non-admin users
      if (!context.params.user.roles.includes('admin')) {
        context.params.query = context.params.query || {};
        context.params.fields = { name: 1, email: 1, _id: 1 };
      }
      return context;
    }]
  }
});

5. Centralize authorization logic

For complex policies, implement a reusable authorization function that validates access against Mongodb metadata before proceeding.

const canAccessDocument = (user, document) => {
  // Example: user must own the document or belong to the document's org
  return document.userId === user.id || user.orgIds.includes(document.orgId);
};

app.service('records').hooks({
  before: {
    get: [async context => {
      const record = await app.service('records').Model.findById(context.id);
      if (!canAccessContext(params.user, record)) {
        throw new Error('Forbidden');
      }
      context.params.record = record;
      return context;
    }]
  }
});

Frequently Asked Questions

How does broken access control manifest differently in Feathersjs services compared to raw Mongodb queries?
In Feathersjs, broken access control often appears as missing or inconsistent authorization hooks, allowing attackers to invoke CRUD methods without proper ownership checks. With Mongodb as the backend, this can lead to unbounded queries or updates if the service does not constrain filters by userId or role. Raw Mongodb access would require direct injection into the database, but through Feathersjs, attackers exploit service endpoints that inadvertently trust client-supplied parameters, making the framework a convenient vector for IDOR and privilege escalation.
Can middleware or hooks alone fully prevent broken access control in Feathersjs with Mongodb?
Middleware and hooks significantly reduce risk but must be consistently applied to all service methods and combined with query validation. You should enforce ownership within each hook, avoid relying on client-provided filters for sensitive fields, and use projection to limit exposed data. Regular scanning with tools that check for missing authorization and improper query constraints helps ensure hooks remain effective across service updates.