Broken Access Control in Nestjs with Mutual Tls
Broken Access Control in Nestjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when API endpoints fail to enforce proper authorization checks, allowing one user to access or modify another user's resources. In NestJS applications that use Mutual TLS (mTLS), the presence of client certificate authentication can create a false sense of security. Developers may assume that because the connection is authenticated via mTLS, authorization is unnecessary or can be simplified. This assumption is incorrect: mTLS provides identity, not permission.
When mTLS is used without explicit, per-request authorization, the application may inadvertently expose endpoints to BOLA/IDOR (Broken Level Authorization / Insecure Direct Object References). For example, an endpoint like GET /users/:userId/profile might validate the client certificate but then directly use the userId from the URL without verifying that the authenticated certificate belongs to that user. An attacker with a valid certificate could iterate over user IDs and access any profile, which middleBrick would flag as a BOLA finding under the Authorization check category.
Additionally, mTLS termination at a proxy or load balancer can complicate context propagation in NestJS. If the framework does not explicitly map the certificate subject or serial number to a user or role, the application may rely on default or missing identity data. This can lead to missing or misapplied role-based access controls (RBAC), especially when combining mTLS with other guards. The 12 parallel security checks in middleBrick test for these gaps by submitting unauthenticated probes that include manipulated identifiers and inspect whether the endpoint enforces ownership or scope checks, independent of transport-layer identity.
Real-world attack patterns relevant to this setup include IDOR via predictable numeric IDs and privilege escalation through misconfigured group claims embedded in certificates. Even with mTLS, endpoints that do not validate scope or tenant boundaries remain vulnerable to OWASP API Top 10 #1 Broken Object Level Authorization. middleBrick’s LLM/AI Security checks also ensure that certificate-based endpoints do not leak system prompts or allow prompt injection when API descriptions are exposed via documentation or introspection endpoints.
Mutual Tls-Specific Remediation in Nestjs — concrete code fixes
To fix Broken Access Control while using mTLS in NestJS, you must couple certificate validation with explicit authorization logic. Do not rely on the presence of a client certificate to enforce permissions. Instead, extract identity from the certificate and enforce ownership or role checks on every request.
Example: mTLS setup in NestJS with explicit identity mapping
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Injectable()
export class CertificateGuard implements CanActivate {
constructor(private readonly authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// Extract identity from the verified client certificate
const cert = request.socket.getPeerCertificate();
if (!cert || Object.keys(cert).length === 0) {
throw new UnauthorizedException('Client certificate required');
}
// Map certificate fields to a user/role, e.g., using the serial number or subject CN
const identity = await this.authService.mapCertificateToUser(cert);
request.user = identity;
return true;
}
}
Example: Enforcing ownership in a user profile endpoint
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { CertificateGuard } from './certificate.guard';
import { UserProfileService } from './user-profile.service';
@Controller('users')
export class UserProfileController {
constructor(private readonly profileService: UserProfileService) {}
@UseGuards(CertificateGuard)
@Get(':userId/profile')
async getProfile(@Param('userId') userId: string, @Req() req) {
const currentUser = req.user; // mapped from certificate in guard
if (currentUser.id !== userId) {
throw new ForbiddenException('You cannot access this profile');
}
return this.profileService.getProfile(userId);
}
}
Example: Applying scope and tenant checks with mTLS in a service
import { Injectable } from '@nestjs/common';
@Injectable()
export class ProjectService {
async getProject(projectId: string, userId: string) {
const project = await this.fetchProjectFromDb(projectId);
if (!project) {
throw new NotFoundException('Project not found');
}
// Ensure the user has access to this project
const membership = await this.checkMembership(userId, project.tenantId);
if (!membership) {
throw new ForbiddenException('Access denied within scope');
}
return project;
}
private async checkMembership(userId: string, tenantId: string): Promise<boolean> {
// Implement RBAC or ABAC check here using certificate-derived identity
return true;
}
}
These examples ensure that mTLS provides authentication, while explicit checks in guards and services enforce authorization. middleBrick’s CLI can validate these controls by scanning the endpoint with varied identifiers to confirm that access is restricted by ownership or scope, not merely by the presence of a certificate.
When integrating with external systems, ensure certificate-to-user mapping is centralized and auditable. The Pro plan’s continuous monitoring can alert you if a new endpoint lacks proper authorization guards, and the GitHub Action can fail builds when a scan detects missing ownership checks, helping you maintain posture as code evolves.