HIGH password sprayingfeathersjsbasic auth

Password Spraying in Feathersjs with Basic Auth

Password Spraying in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability

FeathersJS is a popular framework for building REST and real-time APIs. When Basic Authentication is used without additional protections, the service typically implements a simple username and password check for each request. Password spraying takes advantage of this by using a small number of common passwords across many accounts rather than many passwords against a single account, which helps evade simple rate-based locks.

In a typical FeathersJS service, authentication might be added via an authentication hook that verifies credentials and attaches a user object to the connection. If the service only relies on per-user rate limiting or fails to enforce escalating delays after repeated failures, an attacker can iterate through a list of common passwords for a list of known usernames. Because authentication checks occur before application-level business logic, these requests may still reach the framework’s hook stack, consuming server-side resources and potentially leaking timing information that aids the attacker.

An unauthenticated scan by middleBrick can surface this risk when the authentication check is weak or inconsistent across endpoints. For example, some routes may enforce stricter checks than others, or the service may return different HTTP status codes or response times for valid users versus unknown users. These subtle differences allow an attacker to infer whether a username exists and whether a supplied password is plausible. The framework’s flexibility in hook composition can inadvertently expose authentication paths that do not uniformly enforce protections, such as account lockout or exponential backoff.

Consider the scenario where an attacker uses a list of common passwords like Password1, Welcome1, and Spring2024 against a set of known emails or usernames such as admin@example.com and user@example.com. If FeathersJS returns 401 for bad credentials and 200 for success, and does not introduce random delays or consistent error messaging, the attacker can correlate response codes and timing to refine their guesses. middleBrick’s checks for Authentication and BOLA/IDOR can surface inconsistent enforcement across endpoints, highlighting where authentication logic may be bypassed or trivially iterated.

Additionally, if the application exposes user enumeration through distinct error messages or response behavior, password spraying becomes more efficient. Even without access to the source code, an external scan can detect whether different responses correlate with valid usernames. This is particularly relevant for deployments where Basic Auth is handled at the framework level rather than behind a dedicated identity provider that enforces strict throttling and lockout policies.

Basic Auth-Specific Remediation in Feathersjs — concrete code fixes

To reduce the risk of password spraying, ensure that authentication in FeathersJS is consistent, rate-limited, and hardened against enumeration. Below are concrete code examples that implement secure Basic Auth with protections that mitigate spraying attempts.

Secure Basic Auth Hook Example

Use a custom authentication hook that validates credentials and applies uniform behavior for valid and invalid users. This example uses the @feathersjs/authentication and @feathersjs/authentication-local packages.

// src/hooks/authentication.js
const { AuthenticationService, JWTStrategy, LocalStrategy } = require('@feathersjs/authentication');
const { iff, isProvider } = require('feathers-hooks-common');

module.exports = function (app) {
  const authService = new AuthenticationService(app);

  authService.register('jwt', new JWTStrategy());
  authService.register('local', new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password'
  }));

  app.use('/authentication', authService);

  app.configure(authentication => {
    authentication.hooks({
      authenticate: [
        iff(isProvider('external')), // Only apply for external (HTTP) providers
        (context) => {
          const { email, password } = context.data;
          // Always perform a dummy lookup to prevent timing leaks
          const user = await app.service('users').Model.findOne({ where: { email: 'dummy@example.com' } });
          // Validate credentials using the same logic as for real users
          const isValid = user && user.validatePassword(password);
          if (!isValid) {
            throw new Error('Invalid credentials');
          }
          // Attach normalized user object
          context.result = { user: { id: user.id, email: user.email } };
          return context;
        }
      ]
    });
  });
};

The above hook introduces a dummy lookup to reduce timing differences between valid and invalid users. In production, use a constant-time comparison for password validation and ensure that error messages are generic.

Rate Limiting and Account Protection

Apply global or service-specific rate limiting to authentication endpoints. You can use an Express middleware layer in FeathersJS to throttle requests per IP or per username.

// src/hooks/rate-limit-auth.js
const rateLimit = require('express-rate-limit');

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // limit each IP to 5 authentication requests per window
  keyGenerator: (req) => {
    // Use username if present, otherwise IP
    return req.body.email || req.ip;
  },
  message: 'Too many login attempts, please try again later.',
  standardHeaders: true,
  legacyHeaders: false,
});

module.exports = function () {
  return function (context) {
    const app = context.app;
    app.server.use('/authentication', authLimiter);
    return context;
  };
};

Combine this with account-level protections such as temporary lockout after repeated failures, and ensure that responses do not reveal whether a username exists. Configure hooks to return the same status code and generic message for authentication failures.

Uniform Error Handling

Ensure that all authentication failures return a consistent response shape and HTTP status code. Avoid leaking information through headers or response body differences.

// src/hooks/secure-error-response.js
module.exports = function () {
  return async context => {
    try {
      return await context;
    } catch (error) {
      // Generic error response for authentication failures
      if (error.message === 'Invalid credentials') {
        return { ...context, result: { message: 'Invalid credentials' } };
      }
      throw error;
    }
  };
};

Apply these hooks alongside your existing authentication configuration to reduce the effectiveness of password spraying and enumeration attacks.

Frequently Asked Questions

How does middleBrick detect weak authentication configurations in FeathersJS?
middleBrick runs unauthenticated checks that compare authentication responses across endpoints, looking for inconsistent status codes, timing differences, and user enumeration clues. Findings are mapped to frameworks like OWASP API Top 10 and include remediation guidance.
Can the free plan be used to scan a FeathersJS API for authentication issues?
Yes, the free plan allows 3 scans per month, which is suitable for occasional checks of a FeathersJS endpoint. For continuous monitoring or CI/CD integration, the Pro plan adds scheduled scans and GitHub Action gates.