HIGH broken authenticationfeathersjsapi keys

Broken Authentication in Feathersjs with Api Keys

Broken Authentication in Feathersjs with Api Keys — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework for real-time web applications and REST APIs. When using API keys for authentication, broken authentication can arise from insecure key handling, missing validation, and weak integration with the service layer. An API key is a long-term credential; if it is transmitted over unencrypted channels, stored in client-side code, or accepted without robust validation and scope checks, the API surface becomes vulnerable to unauthorized access.

One common pattern in Feathers is to accept an API key in a header (for example, x-api-key) and use it to identify a service caller. If the service route does not enforce authentication for that key, or if the key is compared using a non-constant-time check, an attacker can enumerate valid keys or escalate privileges. A second vector is key leakage: keys included in URLs or logs may be captured in browser history, server logs, or error messages. A third vector is missing scope/role checks; a key intended for read-only access may be accepted by write routes if the service does not validate permissions on each operation. These issues map to the broken authentication category because the identity proof (the API key) is not verified with sufficient rigor and the authorization boundary around its use is weak.

Consider an unprotected Feathers service that accepts an API key but does not ensure it is tied to a specific set of allowed actions. An attacker who obtains a key (through social engineering, accidental exposure in client-side bundles, or log leakage) can call any method exposed by the service. If the service also exposes sensitive data endpoints without additional context checks (such as ensuring the caller is allowed to access only their own resources), the vulnerability can lead to mass data exposure. The OpenAPI contract may declare security schemes for API keys, but if runtime enforcement is inconsistent or skipped, the spec becomes misleading and the unauthenticated attack surface grows. In such cases, scanning with tools that test both spec-defined expectations and runtime behavior—like checking whether a key is required for sensitive operations—can reveal gaps between declared and actual security.

Real-world attack patterns include enumeration of valid API keys via timing differences or error messages, injection of keys into SSRF payloads if the key is forwarded to internal services, and privilege escalation when higher-privilege keys are accidentally issued to low-privilege users. Because API keys are long-lived secrets, compromise can have a broad impact across the system. Mitigations should focus on secure transmission, strict validation, key scoping, and robust logging without exposing keys in responses or error details.

Api Keys-Specific Remediation in Feathersjs — concrete code fixes

To fix broken authentication when using API keys in FeathersJS, validate the key on every request, enforce scope/role checks, and avoid leaking the key in logs or URLs. Use environment variables to store keys, prefer HTTPS, and ensure your service configuration does not skip authentication for any route.

Example: secure API key authentication in a Feathers service with explicit key lookup and scope validation.

// src/services/notes/notes.class.js or hooks file
const crypto = require('node:crypto');

// Compare keys in constant time to avoid timing attacks
function safeKeyCompare(actual, supplied) {
  return crypto.timingSafeEqual(
    Buffer.from(actual || ''),
    Buffer.from(supplied || '')
  );
}

// In your service hooks
module.exports = {
  before: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

// Example hook to validate API key for all operations
function validateApiKey(keyConfig) {
  return async context => {
    const { app, params } = context;
    const supplied = context.headers['x-api-key'] || context.query.apiKey;
    if (!supplied) {
      throw new Error('Unauthorized: missing API key');
    }

    // Retrieve allowed keys from configuration or a secure store
    const allowedKeys = keyConfig.allowedKeys || [];
    const hasValidKey = allowedKeys.some(k => safeKeyCompare(k, supplied));
    if (!hasValidKey) {
      throw new Error('Unauthorized: invalid API key');
    }

    // Optional: scope/role check
    const keyScopes = keyConfig.scopes && keyConfig.scopes[supplied];
    if (context.method === 'create' || context.method === 'patch' || context.method === 'update' || context.method === 'remove') {
      if (!keyScopes || !keyScopes.write) {
        throw new Error('Forbidden: API key lacks write scope');
      }
    } else if (context.method === 'find' || context.method === 'get') {
      if (!keyScopes || !keyScopes.read) {
        throw new Error('Forbidden: API key lacks read scope');
      }
    }

    // Attach identity to context for downstream use (avoid logging the key)
    context.params.apiKeyId = keyConfig.keyIds && keyConfig.keyIds[supplied];
    return context;
  };
}

// In src/services/notes/notes.js or plugin configuration
const keyConfig = {
  allowedKeys: [
    // Store hashed or plaintext keys from environment; compare safely
    process.env.FEATHERS_API_KEY_1,
    process.env.FEATHERS_API_KEY_2
  ].filter(Boolean),
  scopes: {
    [process.env.FEATHERS_API_KEY_1]: { read: true, write: true },
    [process.env.FEATHERS_API_KEY_2]: { read: true, write: false }
  },
  keyIds: {
    [process.env.FEATHERS_API_KEY_1]: 'key-001',
    [process.env.FEATHER_API_KEY_2]: 'key-002'
  }
};

const notesService = app => new (class NotesService {
  async find(params) {
    // Use params.apiKeyId or validated identity from hooks
    return app.service('notes')._getItems(params);
  }
  async get(id, params) {
    return app.service('notes').get(id, params);
  }
  async create(data, params) {
    if (!params.apiKeyId) throw new Error('Forbidden');
    return app.service('notes').create(data, params);
  }
  async update(id, data, params) {
    if (!params.apiKeyId) throw new Error('Forbidden');
    return app.service('notes').update(id, data, params);
  }
  async patch(id, data, params) {
    if (!params.apiKeyId) throw new Error('Forbidden');
    return app.service('notes').patch(id, data, params);
  }
  async remove(id, params) {
    if (!params.apiKeyId) throw new Error('Forbidden');
    return app.service('notes').remove(id, params);
  }
});

// Register the service with hooks
const notes = notesService(app);
notes.before.hooks.push(validateApiKey(keyConfig));
app.use('/notes', notes);

Additional recommendations: store keys in environment variables or a secrets manager, never in client-side code or logs; enforce HTTPS to prevent interception; rotate keys periodically; and prefer short-lived tokens for high-risk operations where feasible. These steps reduce the risk of broken authentication when relying on API keys in FeathersJS.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Can an API key in a URL or referer header cause broken authentication in FeathersJS?
Yes. If an API key is passed in a URL or referer header, it may be logged in server access logs, browser history, or error reports, leading to leakage and unauthorized use. Always use HTTP headers such as x-api-key and avoid embedding keys in URLs.
How can I test whether my FeathersJS API key implementation is vulnerable to enumeration or privilege escalation?
Use a scanner that performs runtime checks against your OpenAPI spec and runs authenticated and unauthenticated probes. Tests should include timing-based key validation checks, attempts to access write routes with a read-only key, and inspection of error messages for key leakage. The middleBrick CLI can scan your endpoint from the terminal to surface such issues.