HIGH integrity failuresfeathersjsbasic auth

Integrity Failures in Feathersjs with Basic Auth

Integrity Failures in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework for real-time web apps and REST APIs. When Basic Authentication is used directly in a Feathers service without additional integrity controls, the API can be vulnerable to integrity failures. These failures occur when an attacker can tamper with or replay requests in a way that the server accepts as valid, typically because authentication is present but the request context or payload is not adequately protected.

With Basic Auth, credentials are sent in an Authorization header as base64-encoded username:password. Encoding is not encryption; any party that can intercept or access server logs can recover the credentials. If the service does not enforce strict transport integrity (for example, by requiring TLS) and does not validate that each request is tied to a specific, non-replayable context, an attacker who obtains or guesses a valid credential pair can modify resource identifiers or payloads and perform actions on behalf of the authenticated subject.

Feathers allows custom authentication hooks and service logic. If a service relies solely on the presence of Basic Auth headers and does not verify that the authenticated subject is authorized for the specific resource being modified, this can lead to Insecure Direct Object References (IDOR) and Broken Object Level Authorization (BOLA). Combined with missing integrity checks on the request payload (such as ensuring an entity ID belongs to the authenticated subject), an attacker can change numeric IDs in URLs or JSON bodies to access or modify other users’ data.

Integrity failures can also manifest through request replay or missing nonce/version checks. For example, if a Feathers service processes a PATCH request to update a user profile and only checks that a valid Basic Auth credential is present, an attacker could replay that request to revert or alter updates, or inject additional fields if input validation is weak. Because Basic Auth does not provide built-in replay protection, the server must implement its own safeguards, such as requiring idempotency keys or validating ETags/If-Match headers on mutable resources.

The runtime findings from a middleBrick scan can highlight these issues by correlating unauthenticated or weakly authenticated endpoints with missing object-level authorization checks and weak input validation. The scan checks for authentication weaknesses, BOLA/IDOR patterns, and input validation gaps in parallel, which helps surface integrity risks introduced by relying on Basic Auth without complementary controls in FeathersJS services.

Basic Auth-Specific Remediation in Feathersjs — concrete code fixes

To address integrity failures when using Basic Auth in FeathersJS, combine transport enforcement, strict authentication hooks, per-request authorization, and input validation. Below are concrete code examples that demonstrate a more secure approach.

1. Enforce TLS and validate the Authorization header

Ensure your server only accepts requests with valid Basic Auth over HTTPS. Reject requests that do not present a properly formatted header.

// src/hooks/require-secure-basic-auth.js
module.exports = function requireSecureBasicAuth() {
  return async context => {
    const { headers } = context.params;
    if (!headers || !headers.authorization) {
      throw new Error('Unauthorized: Missing Authorization header');
    }
    const [scheme, credentials] = headers.authorization.split(' ');
    if (scheme !== 'Basic' || !credentials) {
      throw new Error('Unauthorized: Invalid Authorization scheme');
    }
    // Optionally, decode and validate credentials here or let auth service handle it
    context.params.basicAuthHeader = headers.authorization;
    return context;
  };
};

2. Use a custom authentication hook that maps credentials to a user record and attaches it to the context

Decode the credentials, verify them against a data store, and attach a normalized user object to the context for downstream authorization checks.

// src/hooks/basic-auth-user.js
const { Buffer } = require('buffer');

module.exports = function basicAuthUser({ userModel }) {
  return async context => {
    const authHeader = context.params.basicAuthHeader;
    if (!authHeader) { return context; }
    const [, base64] = authHeader.split(' ');
    const decoded = Buffer.from(base64, 'base64').toString('utf8');
    const [username, password] = decoded.split(':');
    if (!username || !password) {
      throw new Error('Unauthorized: Invalid credentials format');
    }
    const user = await userModel.findOne({ query: { username } });
    if (!user || user.password !== password) { // in practice, use a hashed comparison
      throw new Error('Unauthorized: Invalid credentials');
    }
    context.params.user = user;
    return context;
  };
};

3. Enforce object-level authorization in service hooks

For each operation, verify that the authenticated user has the right to access or modify the target resource. This prevents BOLA/IDOR even when Basic Auth is valid.

// src/hooks/ensure-owns-resource.js
module.exports = function ensureOwnsResource({ userModel }) {
  return async context => {
    const user = context.params.user;
    if (!user) { return context; }
    const id = context.id;
    if (typeof id !== 'string' && typeof id !== 'number') {
      // For non-identifier operations (like GET /), allow as needed
      return context;
    }
    const resource = await userModel.findOne({ query: { _id: id, userId: user._id } });
    if (!resource) {
      throw new Error('Forbidden: You do not own this resource');
    }
    // Optionally attach resource to context for downstream use
    context.params.resource = resource;
    return context;
  };
};

4. Apply input validation and consider idempotency for mutating methods

Validate and sanitize all inputs, and for critical updates require an If-Match/ETag or an idempotency key to reduce the impact of accidental or malicious replays.

// Example validation using feathers-hooks-common
const { iff, isProvider, preventChanges } = require('feathers-hooks-common');
const { sanitize } = require('feathers-commons');

module.exports = {
  before: {
    all: [],
    find: [ preventChanges(['password']) ],
    get: [ iff(isProvider('external'), sanitize()) ],
    create: [ sanitize() ],
    update: [ iff(isProvider('external'), sanitize()) ],
    patch: [ iff(isProvider('external'), sanitize()) ],
    remove: []
  },
  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

5. Example service configuration integrating hooks

Wire the hooks into a Feathers service to ensure that authentication and integrity checks are applied consistently.

// src/services/users/users.service.js
const { authenticate } = require('@feathersjs/authentication');
const { hooks } = require('feathers-hooks-common');

module.exports = function (app) {
  const options = {
    name: 'users',
    paginate: { default: 10, max: 25 }
  };

  app.use('/users', {
    async find(params) {
      // Example of a find that ensures the authenticated user only sees their own records
      const user = params.user;
      return app.service('users').Model.find({ where: { userId: user._id } });
    },
    async get(id, params) {
      const user = params.user;
      const record = await app.service('users').Model.findByPk(id);
      if (!record || record.userId !== user._id) {
        throw new Error('Not found');
      }
      return record;
    },
    async create(data, params) {
      const user = params.user;
      return app.service('users').Model.create({ ...data, userId: user._id });
    },
    async patch(id, data, params) {
      const user = params.user;
      const record = await app.service('users').Model.findByPk(id);
      if (!record || record.userId !== user._id) {
        throw new Error('Forbidden or Not found');
      }
      return record.update(data);
    },
    async remove(id, params) {
      const user = params.user;
      const record = await app.service('users').Model.findByPk(id);
      if (!record || record.userId !== user._id) {
        throw new Error('Forbidden or Not found');
      }
      return record.destroy();
    }
  });

  const authHook = authenticate('basic');
  const enrichUser = hooks(basicAuthUser({ userModel: app.service('users').Model }));
  const ownsResource = hooks(ensureOwnsResource({ userModel: app.service('users').Model }));

  app.service('users').hooks({
    before: {
      all: [authHook, enrichUser, ownsResource],
      find: [],
      get: [],
      create: [],
      patch: [],
      remove: []
    }
  });
};

Frequently Asked Questions

Does middleBrick fix integrity failures found in FeathersJS with Basic Auth?
middleBrick detects and reports integrity failures and provides remediation guidance; it does not automatically fix or patch vulnerabilities.
Can I use the free tier to scan FeathersJS APIs using Basic Auth?
Yes, the free tier allows 3 scans per month, which is suitable for trying out scans against FeathersJS endpoints using Basic Auth.