HIGH api key exposurefeathersjsopenid connect

Api Key Exposure in Feathersjs with Openid Connect

Api Key Exposure in Feathersjs with Openid Connect — how this specific combination creates or exposes the vulnerability

FeathersJS is a popular framework for building REST and real-time APIs with JavaScript and Node.js. When integrating OpenID Connect (OIDC) for authentication, developers often store client secrets or API keys in server-side configuration or pass them to frontend clients inadvertently. This combination can lead to API key exposure when keys are embedded in client-side bundles, logged in error messages, or transmitted over unencrypted channels due to misconfigured OIDC flows.

In a FeathersJS application using an OIDC strategy such as @feathersjs/authentication with an OpenID Connect provider, the framework typically handles token validation and user identity mapping. However, if the OIDC client configuration (e.g., client ID and client secret) is stored in a file like src/authentication.js and accidentally included in a production build or exposed via source code repositories, it becomes an API key exposure. Additionally, FeathersJS services may return sensitive metadata or keys in responses if authorization rules are not explicitly defined, especially when integrating with OIDC identity payloads that include proprietary attributes.

Another exposure vector arises when the OIDC flow uses implicit grant for SPAs (Single Page Applications). Implicit grant returns tokens in the URL fragment, which can be leaked in browser history, server logs, or referrer headers. If the FeathersJS backend uses these tokens to call downstream services and passes API keys as query parameters or headers without encryption, those keys can be intercepted. Real-world examples include misconfigured proxy integrations where API keys intended for server-side use are forwarded to third-party endpoints from the client side, leading to compromise.

Consider a scenario where a FeathersJS service retrieves an external API key from a configuration module to call a payment gateway. If the OIDC user object is merged into the service parameters and the key is attached to the response, an attacker authenticated via OIDC could retrieve the key through a crafted request. This aligns with common API security findings such as BFLA (Business Logic Flaws and Abuse), where improper authorization allows subject-level data leakage. Such exposures violate secure-by-design principles and can lead to unauthorized access to backend systems or third-party services.

OpenID Connect-specific misconfigurations, such as not validating the aud (audience) claim or failing to restrict allowed issuers, can also cause FeathersJS to accept tokens intended for other clients. If the backend uses a generic key validation approach, it might inadvertently propagate API keys to unauthorized contexts. Security scanners that test authentication and property authorization, like those performed during a middleBrick scan, can detect these classes of issues by correlating OIDC identity data with service output and configuration patterns.

Finally, logging practices in FeathersJS can inadvertently expose API keys when OIDC token introspection or error traces include sensitive configuration values. Even if the framework does not log keys directly, custom hooks or third-party libraries might serialize the entire authentication context, including keys, into logs or error responses. These exposures are detectable through runtime analysis that maps configuration definitions to runtime outputs, a capability emphasized in frameworks supporting OpenAPI spec analysis with full $ref resolution.

Openid Connect-Specific Remediation in Feathersjs — concrete code fixes

To remediate API key exposure in FeathersJS with OpenID Connect, apply strict separation between server-side secrets and client-facing code, enforce token validation, and ensure services respect authorization policies. Below are concrete code examples demonstrating secure configurations.

Secure OIDC Authentication Setup

Use the FeathersJS authentication module with explicit configuration that avoids exposing secrets to the client. Store client secrets in environment variables and reference them securely.

// src/authentication.js
const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { ExpressProvider } = require('@feathersjs/authentication-oauth');

module.exports = function (app) {
  const authentication = new AuthenticationService({
    entity: 'user',
    service: app.service('authentication'),
    secret: process.env.FEATHERS_SECRET
  });

  authentication.configure('jwt', new JWTStrategy({
    secret: process.env.JWT_SECRET
  }));

  authentication.configure('oauth2', new ExpressProvider({
    entity: 'user',
    service: app.service('auth-oauth2'),
    setup: (app) => {
      app.enable('auth');
    },
    providers: {
      oidc: {
        strategy: require('feathers-authentication-oidc').Strategy,
        options: {
          authorizationURL: process.env.OIDC_AUTH_URL,
          clientID: process.env.OIDC_CLIENT_ID,
          clientSecret: process.env.OIDC_CLIENT_SECRET,
          callbackURL: process.env.OIDC_CALLBACK_URL,
          scope: 'openid profile email',
          responseType: 'code',
          passReqToCallback: true
        }
      }
    }
  }));

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

This configuration ensures the client secret is read from the environment and never serialized to the frontend. The responseType: 'code' enforces the authorization code flow, avoiding token exposure in URLs.

Service-Level Authorization to Prevent Key Leakage

Define hooks that sanitize responses and validate OIDC token claims before returning data. This prevents API keys from being included in service results, even if they are present in the request context.

// src/hooks/no-key-exposure.js
module.exports = function () {
  return async context => {
    const { user } = context.params.auth;
    if (user && user.roles && !user.roles.includes('admin')) {
      // Remove sensitive fields from output
      delete context.result.apiKey;
      delete context.result.internalToken;
    }
    return context;
  };
};

Apply this hook to services that interact with external APIs requiring keys:

// src/services/external-api/external-api.service.js
const { iff, isProvider } = require('feathers-hooks-common');
const removeSensitiveData = require('./hooks/no-key-exposure');

module.exports = function (app) {
  app.use('/external-api', createService({
    Model: ExternalModel,
    paginate: { default: 10, max: 50 }
  }));

  const externalApiService = app.service('/external-api');
  externalApiService.hooks({
    before: {
      all: []
    },
    after: {
      all: [iff(isProvider('external'), removeSensitiveData)]
    },
    error: {
      all: []
    }
  });
};

This hook ensures that keys are not returned in responses to non-admin users, addressing property authorization concerns. It also demonstrates how to scope hooks to specific providers to avoid affecting other service paths.

OIDC Token Validation and Audience Checks

Validate the audience and issuer on the backend before using any token to access protected resources. FeathersJS hooks can integrate with libraries like jwks-rsa to verify tokens securely.

// src/hooks/validate-oidc-token.js
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');

const client = jwksClient({
  jwksUri: process.env.OIDC_JWKS_URI
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) { return callback(err); }
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

module.exports = function (app) {
  return async context => {
    const authHeader = context.params.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new Error('Unauthenticated');
    }
    const token = authHeader.split(' ')[1];
    const decoded = jwt.decode(token, { complete: true });
    if (!decoded || decoded.header.alg !== 'RS256') {
      throw new Error('Invalid token');
    }
    const publicKey = await new Promise((resolve, reject) => {
      getKey(decoded.header, (err, key) => {
        if (err) { return reject(err); }
        resolve(key);
      });
    });
    const payload = jwt.verify(token, publicKey, {
      audience: processOIDC_CLIENT_ID,
      issuer: process.env.OIDC_ISSUER
    });
    context.params.auth.user = payload;
    return context;
  };
};

This hook enforces strict token validation, reducing the risk of accepting tampered or misissued tokens that could lead to unauthorized key usage.

By combining secure configuration, response sanitization, and rigorous token validation, FeathersJS applications can mitigate API key exposure risks associated with OpenID Connect integrations.

Frequently Asked Questions

How can I prevent API keys from being exposed in FeathersJS logs when using OpenID Connect?
Ensure hooks sanitize outputs and avoid logging full authentication contexts. Use environment variables for secrets and configure FeathersJS to exclude sensitive fields from logs.
Is implicit grant safe for OpenID Connect with FeathersJS?
No. Implicit grant exposes tokens in URLs, increasing leakage risk. Use the authorization code flow with response_type=code and secure server-side token handling.