Api Key Exposure in Nestjs with Openid Connect
Api Key Exposure in Nestjs with Openid Connect — how this specific combination creates or exposes the vulnerability
When an API built with NestJS uses OpenID Connect (OIDC) for authentication, developers often integrate an OIDC client library and configure endpoints like /auth/login/callback. If API keys meant for backend-to-backend or third-party service authentication are stored in environment variables, configuration files, or source code, and those keys are accidentally exposed in logs, error messages, or client-side bundles, the combination of NestJS and OIDC can inadvertently surface those keys.
For example, if a NestJS service validates OIDC ID tokens and, upon validation success, calls an external API using a stored API key, the key may be present in process.env. Should the application include verbose error handling that dumps environment variables or configuration into responses (e.g., during failed token introspection), an attacker leveraging an authenticated session (perhaps via a misconfigured OIDC redirect or a leaked session) could trigger error paths that reveal the key. Additionally, if the OIDC integration relies on a discovery document and the application caches or logs metadata URLs, incorrect handling may expose internal URLs or keys used for service-to-service calls.
Another scenario involves improperly configured OIDC scopes and claims. If NestJS requests openid profile scope and maps claims into application state without isolating authentication context from service credentials, a confused-deployment risk arises where tokens meant for frontend consumption are mistakenly used to authorize backend API key usage. This can become evident when runtime findings from a middleBrick scan show unauthenticated endpoints that expose environment-derived values or where rate limiting is inconsistent, enabling enumeration of valid keys through timing or error-difference attacks.
Furthermore, if the NestJS application serves static assets or debug pages and references API keys in JavaScript bundles (e.g., via import.meta.env), an OIDC-aware attacker who can execute JavaScript in a victim’s browser may exfiltrate keys through social engineering or malicious browser extensions. middleBrick testing against such an API would flag findings related to Data Exposure and Unsafe Consumption, highlighting endpoints that return configuration or environment-derived values without proper authorization controls.
In this context, it is important to remember that middleBrick detects and reports these risky patterns without fixing them. The scanner will identify that an endpoint returns sensitive data or lacks proper authorization checks, providing remediation guidance rather than automatically resolving the exposure.
Openid Connect-Specific Remediation in Nestjs — concrete code fixes
To securely integrate OpenID Connect in NestJS while mitigating API key exposure, follow these patterns that isolate authentication flows from service credentials and avoid leaking sensitive values.
First, keep API keys out of the runtime environment served to the client. Store them in server-side secrets and access them only in controlled contexts, never within error responses or logs. Use NestJS guards and interceptors to ensure that endpoints requiring API keys are invoked only after successful OIDC validation and proper role/authorization checks.
Example OIDC configuration in NestJS using an OAuth2 strategy (e.g., with passport-openid-connect or a custom strategy):
// auth/oidc.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-openid-connect';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
constructor(private readonly configService: ConfigService) {
super({
issuer: configService.get('OIDC_ISSUER'),
authorizationURL: configService.get('OIDC_AUTH_URL'),
tokenURL: configService.get('OIDC_TOKEN_URL'),
userInfoURL: configService.get('OIDC_USERINFO_URL'),
clientID: configService.get('OIDC_CLIENT_ID'),
clientSecret: configService.get('OIDC_CLIENT_SECRET'),
callbackURL: 'https://api.example.com/auth/login/callback',
scope: ['openid', 'profile', 'email'],
});
}
async validate(accessToken: string, refreshToken: string, profile: any) {
// Map minimal claims needed for authentication
return { sub: profile.id, email: profile.email };
}
}
Ensure that the callback endpoint does not echo environment variables:
// auth/auth.controller.ts
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@ApiBearerAuth()
@UseGuards(AuthGuard('oidc'))
@Get('login/callback')
async callback(@Req() req) {
// Do not include process.env or internal config in the response
return { user: req.user };
}
}
When calling external APIs that require an API key, retrieve the key at invocation time from a secure vault or a server-side configuration that is not accessible to the client. Avoid logging the key, and ensure that error handling does not include stack traces or config dumps:
// external/external.service.ts
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class ExternalService {
constructor(
private readonly http: HttpService,
private readonly configService: ConfigService,
) {}
async callProtectedResource() {
const apiKey = this.configService.get('EXTERNAL_API_KEY');
try {
const response = await this.http.get('https://vendors.example.com/resource', {
headers: { Authorization: `Bearer ${apiKey}` },
}).toPromise();
return response;
} catch (error) {
// Avoid exposing apiKey in errors
throw new Error('External service request failed');
}
}
}
Validate ID tokens strictly and map claims without mixing them with service credentials. Use middleware to strip or redact sensitive environment values from any accidental exposure paths, and ensure that OIDC discovery metadata is not cached or logged in a way that could aid an attacker in mapping internal services.
Finally, use middleBrick to validate that your remediation works as intended. Run scans against your endpoints to confirm that no API keys appear in responses, that error paths do not divulge configuration, and that endpoints with sensitive keys are protected by proper authentication and authorization checks.