HIGH api key exposurehapiopenid connect

Api Key Exposure in Hapi with Openid Connect

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

Hapi is a rich framework for building web services in Node.js, and it is commonly integrated with OpenID Connect to delegate authentication to an identity provider. When this integration is not carefully designed, API keys or other bearer secrets can be mishandled, leading to exposure. The risk typically arises when route handlers or plugin configurations inadvertently log, reflect, or forward authorization headers that contain both an OpenID Connect access token and an API key used for downstream service calls.

In a Hapi application using OpenID Connect, the framework validates tokens via an authentication strategy (e.g., using hapi-auth-jwt2 or a custom OpenID Connect plugin). If the application merges the incoming Authorization bearer token with an API key stored in configuration or retrieved from a secrets manager, and then logs the combined header or includes it in error messages, the API key can be exposed to unauthorized parties. For example, a developer might log request headers for debugging and include the Authorization header, inadvertently publishing the API key if it is passed alongside the OpenID Connect access token.

Another exposure path involves improper propagation of credentials to upstream services. A Hapi route that calls a downstream API might append an API key as a query parameter or custom header while also forwarding the incoming OpenID Connect access token. If the upstream service’s response or error is reflected back to the client, the API key can be revealed in logs or browser developer tools. This is particularly risky when the downstream API expects the key in a predictable location, such as a query string, which can be captured by network observers or server-side log aggregation systems.

Middleware or custom authentication schemes that copy headers without strict filtering can also contribute to exposure. In Hapi, it is possible to inadvertently pass through headers intended only for the OpenID Connect provider to downstream services. If an API key is stored in a header like x-api-key and the application logic forwards all incoming authorization-related headers without validation, the key may be exposed to external endpoints that do not require it. This violates the principle of least privilege and increases the attack surface.

The combination of OpenID Connect and API keys demands careful segregation of concerns: the access token should be used for identity and authorization decisions within the application, while the API key should be used only where strictly necessary and never be reflected to the client or logged. Without explicit controls in Hapi route definitions and plugin configurations, developers risk creating a channel through which API keys are inadvertently surfaced, enabling credential theft or unauthorized access to backend services.

To detect such issues, scanning tools examine whether the application’s runtime behavior reflects sensitive credentials in responses, headers, or logs, and whether OpenAPI specifications accurately describe which endpoints accept or propagate API keys. This helps identify mismatches between intended authentication flows and actual implementation in Hapi services that rely on OpenID Connect.

OpenID Connect-Specific Remediation in Hapi — concrete code fixes

Remediation focuses on strict header management, proper token handling, and avoiding the mixing of authentication mechanisms. In Hapi, you should separate the concerns of identity verification (OpenID Connect) and service-to-service authentication (API keys). Below are concrete code examples that demonstrate secure patterns.

1. Configure OpenID Connect without exposing API keys in logs or errors

Ensure that the authentication strategy validates the OpenID Connect token but does not propagate sensitive headers. Use route options to limit which headers are preserved and avoid logging authorization headers.

const Hapi = require('@hapi/hapi');
const Jwt = require('hapi-auth-jwt2');

const server = Hapi.server({ port: 4000, host: 'localhost' });

await server.register(Jwt);

server.auth.strategy('jwt', 'jwt', {
  key: 'your-jwks-uri-or-key',
  validate: (decoded, request, h) => {
    // Validate scopes, issuer, audience as needed
    return { isValid: true, credentials: decoded };
  },
  verifyOptions: {
    algorithms: ['RS256'],
    issuer: 'https://your-idp.example.com',
    audience: 'hapi-api'
  }
});

server.ext('onPreResponse', (request, h) => {
  const response = request.response;
  if (response.isBoom) {
    // Strip sensitive headers from error responses
    response.headers['x-api-key'] = undefined;
  }
  return h.continue;
});

server.route({
  method: 'GET',
  path: '/secure-data',
  options: {
    auth: 'jwt',
    handler: (request, h) => {
      // Do not log request.headers.Authorization or request.headers['x-api-key']
      return { data: 'protected resource' };
    }
  }
});

await server.start();

2. Use API keys only for downstream calls, never reflect them to the client

When a Hapi route needs to call a downstream service that requires an API key, retrieve the key from a secure configuration or secrets manager and ensure it is not included in responses or logs.

const axios = require('axios');

server.route({
  method: 'GET',
  path: '/external',
  options: {
    auth: 'jwt',
    handler: async (request, h) => {
      const apiKey = process.env.DOWNSTREAM_API_KEY;
      try {
        const res = await axios.get('https://api.external.com/data', {
          headers: { 'x-api-key': apiKey },
          timeout: 5000
        });
        return res.data;
      } catch (err) {
        // Do not expose apiKey in error messages
        request.log(['api', 'error'], 'external call failed');
        throw h.response('service unavailable').code(503);
      }
    }
  }
});

3. Validate and filter forwarded headers in server routes or plugins

If you must forward headers, explicitly allowlist only the required headers and remove API key headers before forwarding to external services or returning to the client.

server.ext('onPreHandler', (request, h) => {
  const forbidden = ['x-api-key', 'authorization'];
  const headers = request.headers;
  forbidden.forEach(key => {
    if (headers[key]) {
      request.log(['security', 'header'], \`dropping sensitive header: ${key}\`);
      delete headers[key];
    }
  });
  return h.continue;
});

4. Define an OpenAPI spec that distinguishes identity and service credentials

Describe which endpoints rely on OpenID Connect and which (if any) require API keys, ensuring that the spec does not imply that access tokens can be used as API keys.

paths:
  /secure-data:
    get:
      summary: Get protected data
      securitySchemes:
        bearerAuth:
          type: http
          scheme: bearer
          bearerFormat: JWT
      responses:
        '200':
          description: OK
  /external:
    get:
      summary: Call downstream service
      securitySchemes:
        bearerAuth:
          type: http
          scheme: bearer
          bearerFormat: JWT
      responses:
        '200':
          description: OK
security:
  - bearerAuth: []

By applying these patterns, a Hapi service using OpenID Connect can mitigate the risk of API key exposure while maintaining clear separation between identity verification and service authentication.

Frequently Asked Questions

Can OpenID Access Tokens be used as API keys in Hapi?
No. Access tokens identify the user and should not be used as service-to-service API keys. They serve different purposes and must be handled separately to avoid credential confusion and exposure.
How can I detect if my Hapi application is leaking API keys in responses or logs?
Use automated scans that inspect runtime behavior for sensitive headers in responses and logs, and review your Hapi route handlers and plugins to ensure API keys are not included in error messages or forwarded without filtering.