Broken Authentication in Nestjs with Api Keys
Broken Authentication in Nestjs with Api Keys — how this specific combination creates or exposes the vulnerability
Broken Authentication occurs when application functions related to authentication are not implemented correctly, allowing attackers to compromise passwords, keys, or session tokens. Using static API keys in a NestJS application can create or expose authentication weaknesses when keys are transmitted over unencrypted channels, stored in source control, or handled without proper validation and scope controls.
In NestJS, API keys are often passed via HTTP headers (e.g., x-api-key). If the API key is accepted without verifying its scope, rate limiting, or association with a specific permission set, an attacker who obtains the key can perform actions on behalf of the key owner. This becomes a Broken Authentication risk when the key is treated as a permanent credential without rotation, revocation, or binding to a specific identity or context. For example, a key intended for read-only access might be accepted by endpoints that perform state-changing operations, enabling privilege escalation.
Another common pattern is middleware that extracts the key and attaches user information to the request for downstream use. If this process does not validate the key against a trusted data store on each request, an attacker could tamper with the request context or reuse intercepted keys. A misconfigured CORS policy can also expose API keys to browser-based scripts, enabling cross-origin misuse. When combined with weak transport security (missing or misconfigured HTTPS), keys can be captured in transit, leading to session hijacking. These issues map to the OWASP API Top 10 category Broken Authentication and can be surfaced by middleBrick’s Authentication and BOLA/IDOR checks, which analyze how keys and tokens are validated and whether authorization is rechecked per operation.
Consider an endpoint that relies solely on the presence of an API key to authorize a data export. If the key is not checked against an inventory of allowed operations, an attacker can use the same key to access administrative endpoints, demonstrating BFLA/Privilege Escalation. middleBrick’s BFLA/Privilege Escalation and Property Authorization checks are designed to detect these gaps by comparing declared permissions in an OpenAPI spec with observed runtime behavior, including how keys are accepted and whether per-request authorization is enforced.
Static API keys also introduce risk if they appear in logs, error messages, or client-side code. Output scanning capabilities included in tools like middleBrick’s LLM/AI Security checks look for accidental exposure of secrets in model outputs or error traces. Because middleBrick scans unauthenticated attack surfaces and resolves OpenAPI $ref structures, it can identify mismatches between documented authentication methods and actual endpoint behavior, such as missing security schemes or overly permissive paths.
Real-world attack patterns such as credential stuffing or token replay can occur when API keys are treated as low-entropy secrets or shared across services without isolation. For example, using the same key across multiple client applications means that a breach in one environment can affect others. middleBrick’s Inventory Management and Data Exposure checks help surface these risks by analyzing how keys are referenced across the API surface and whether sensitive responses are returned over insecure channels.
Api Keys-Specific Remediation in Nestjs — concrete code fixes
Remediation focuses on validating each API key against a trusted source on every request, enforcing scope and rate limits, and ensuring keys are never treated as immutable credentials. Below are concrete, working examples that demonstrate secure handling of API keys in NestJS.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class ApiKeyValidationMiddleware implements NestMiddleware {
private readonly validKeys = new Set([
process.env.API_KEY_READONLY as string,
process.env.API_KEY_WRITE as string,
]);
use(req: Request, res: Response, next: NextFunction) {
const key = req.headers['x-api-key'] as string | undefined;
if (!key || !this.validKeys.has(key)) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
// Attach minimal, verified metadata; avoid inferring permissions solely from key presence
req.apiKey = { key };
next();
}
}
In this example, keys are compared against values stored as environment variables, avoiding hardcoded secrets. The middleware performs constant-time validation by using a Set and does not infer authorization beyond the key itself; additional authorization checks must be applied at the route or service layer to enforce scope.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ApiKeyValidationMiddleware } from './api-key-validation.middleware';
@Injectable()
export class KeyScopeGuard implements CanActivate {
constructor(private readonly apiKeyValidationMiddleware: ApiKeyValidationMiddleware) {}
async canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
this.apiKeyValidationMiddleware.use(req, res, () => {
// After validation, enforce scope per endpoint
const requestScope = req.headers['x-scope'] as string | undefined;
const handler = context.getClass().getName();
const method = context.getHandler().name;
if (this.hasScope(requestScope, handler, method)) {
return true;
}
res.status(403).json({ error: 'Insufficient scope' });
return false;
});
return false; // Middleware already handled response
}
private hasScope(scope: string | undefined, handler: string, method: string): boolean {
// Map handlers to required scopes; keep rules explicit and maintainable
const scopeMap: Record = {
ReportsController_getReport: ['read:reports'],
AdminController_purge: ['write:admin'],
};
const required = scopeMap[`${handler}_${method}`];
return required ? required.includes(scope) : false;
}
}
This guard validates the key first, then enforces scope based on an explicit map. It avoids relying on the key alone to determine what operations are allowed, addressing Property Authorization and BOLA/IDOR risks. Scope values should be passed in a separate header and never inferred from the key itself.
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { ApiKeyValidationMiddleware } from './api-key-validation.middleware';
import { KeyScopeGuard } from './key-scope.guard';
import { ReportsController } from './reports/reports.controller';
import { AdminController } from './admin/admin.controller';
@Module({
controllers: [ReportsController, AdminController],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(ApiKeyValidationMiddleware)
.forRoutes(
{ path: 'reports', method: RequestMethod.GET },
{ path: 'admin', method: RequestMethod.POST },
)
.apply(KeyScopeGuard)
.forRoutes(
{ path: 'reports', method: RequestMethod.GET },
{ path: 'admin', method: RequestMethod.POST },
);
}
}
Use HTTPS to protect keys in transit and rotate keys regularly. Do not log keys, and avoid returning them in error messages. middleBrick’s CLI can be used to scan endpoints and verify that authentication checks are consistently applied; the GitHub Action can enforce a minimum score before merges, and the Dashboard can track changes over time to ensure remediation remains effective.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |