HIGH broken access controlfeathersjsbasic auth

Broken Access Control in Feathersjs with Basic Auth

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

FeathersJS is a framework for creating JavaScript and TypeScript APIs. Its service architecture and hooks system simplify building CRUD endpoints, but it does not enforce access controls by default. When Basic Auth is used for authentication, the framework relies on the application to validate credentials and to enforce what a given identity is permitted to do. If authorization checks are missing or applied inconsistently, the API exhibits Broken Access Control.

In FeathersJS, a typical misconfiguration is to add the @feathersjs/authentication and @feathersjs/authentication-client packages and basic auth hooks, then rely on the presence of a token or user object without verifying scope or ownership. For example, consider a service for user profiles:

// services/profiles/profiles.service.js
const { Service } = require('feathersjs');

class ProfilesService extends Service {
  find(params) {
    // Risk: returns all profiles without checking who is requesting what
    return super.find(params);
  }

  get(id, params) {
    // Risk: allows any authenticated user to fetch any profile by ID
    return super.get(id, params);
  }
}

module.exports = function () {
  const app = this;
  app.use('/profiles', new ProfilesService());
};

If this service only authenticates via Basic Auth (e.g., using @feathersjs/authentication-local with a username and password), an authenticated user can enumerate other user IDs and read or modify data they should not access. This is a classic BOLA/IDOR pattern enabled by missing authorization checks. Basic Auth provides credentials, but credentials alone do not define permissions. Without explicit checks tying a user to the data they own or are allowed to access, the API exposes information and functionality across users.

Another common scenario involves role or scope fields stored in the user object after Basic Auth verification. If the authorization logic does not inspect roles or permissions and instead trusts that any authenticated user can invoke any method, an attacker can call admin-only endpoints by simply authenticating with a low-privilege Basic Auth credential and making crafted requests. The framework does not automatically restrict methods based on identity attributes; developers must implement this. Missing such checks results in Privilege Escalation via BFLA, where an attacker elevates from a read-only user to an administrative function by targeting weakly guarded endpoints.

Furthermore, if the service methods do not scope queries to the authenticated user, the find method might return records belonging to other users. For instance, a query like app.service('profiles').find() without a $filter on user ID can expose data broadly. In a FeathersJS app using Basic Auth, the responsibility is on the service implementation to ensure that operations respect the authenticated subject’s permissions and that the subject is properly derived from the Basic Auth credentials rather than client-supplied identifiers.

Basic Auth-Specific Remediation in Feathersjs — concrete code fixes

To secure FeathersJS with Basic Auth, enforce authorization at the service method level and scope data access to the authenticated subject. Use hooks to inject the authenticated user into params and apply record-level filtering. Below are concrete code examples that demonstrate a secure pattern.

1) Define a hook that enforces ownership and role-based checks. This hook ensures that users can only access their own profiles unless they have elevated permissions:

// hooks/authorization.js
const { iff, isProvider, preventChanges } = require('feathers-hooks-common');

module.exports = {
  before: {
    async all(context) {
      const { user } = context;
      if (!user) {
        throw new Error('Unauthenticated');
      }

      // For profile services, scope to the user’s own record
      if (context.path === 'profiles') {
        context.params.query = context.params.query || {};
        // If querying by ID, ensure it matches the user’s ID
        if (typeof context.id !== 'undefined') {
          context.params.query._id = context.id;
        }
        // Ensure the query filters by the user ID
        context.params.query.userId = user._id;
      }
    },
    find: [],
    get: [iff(isProvider('external'), (context) => { 
      if (context.id !== context.data._id) {
        throw new Error('Unauthorized');
      }
    })],
    create: [preventChanges('user')],
    update: [preventChanges('user')],
    patch: [preventChanges('user')],
    remove: [preventChanges('user')]
  },

  after: {
    all: [],
    find: (context) => {
      // Ensure responses only contain allowed data
      return context;
    },
    get: (context) => {
      if (!context.data || context.data.userId !== context.params.user._id) {
        throw new Error('Not found');
      }
      return context;
    }
  },

  error: []
};

2) Apply the hook to the service and ensure Basic Auth populates context.params.user. Configure authentication and local login correctly:

// app.js
const authentication = require('@feathersjs/authentication');
const local = require('@feathersjs/authentication-local');
const { iff, isProvider } = require('feathers-hooks-common');

const app = require('feathers')();
app.configure(authentication({
  secret: 'your-secret',
  entity: 'user',
  authStrategies: ['local']
}));

app.use('/authentication', local.authentication({
  usernameField: 'username',
  passwordField: 'password',
  entity: 'user',
  service: 'users'
}));

// Apply authorization hook to profiles service
const profileAuthHook = require('./hooks/authorization');
app.use('/profiles', new ProfilesService());
app.service('profiles').hooks({
  before: {
    all: [profileAuthHook.before.all],
    find: [profileAuthHook.before.find],
    get: [profileAuthHook.before.get],
    create: [profileAuthHook.before.create],
    update: [profileAuthHook.before.update],
    patch: [profileAuthHook.before.patch],
    remove: [profileAuthHook.before.remove]
  },
  after: {
    all: [profileAuthHook.after.all],
    find: [profileAuthHook.after.find],
    get: [profileAuthHook.after.get]
  }
});

// Example login route that sets user in params for subsequent hooks
app.use('/login', local.login({
  successRedirect: null,
  failureRedirect: null
}));

3) Ensure that services do not rely on client-supplied IDs for sensitive operations. Instead, derive the subject from the authenticated user in params:

// services/profiles/profiles.service.js
const { Service } = require('feathersjs');

class ProfilesService extends Service {
  async get(id, params) {
    const { user } = params;
    if (!user) {
      throw new Error('Unauthorized');
    }
    // Scope to the authenticated user’s record
    const record = await this.Model.findOne({ where: { id, userId: user._id } });
    if (!record) {
      throw new Error('Not found');
    }
    return record;
  }
}

module.exports = function () {
  const app = this;
  app.use('/profiles', new ProfilesService());
};

These patterns ensure that Basic Auth provides credentials while explicit authorization checks enforce permissions and data scope, addressing Broken Access Control in FeathersJS.

Frequently Asked Questions

Can middleBrick detect Broken Access Control in a FeathersJS API using Basic Auth?
Yes. middleBrick runs a dedicated Authorization check that tests for missing or weak access controls, including BOLA/IDOR and Privilege Escalation, and reports findings with remediation guidance.
Does middleBrick fix the vulnerabilities it finds?
No. middleBrick detects and reports issues with actionable remediation guidance. It does not automatically fix, patch, block, or remediate vulnerabilities.