HIGH credential stuffingnestjsmutual tls

Credential Stuffing in Nestjs with Mutual Tls

Credential Stuffing in Nestjs with Mutual Tls — how this specific combination creates or exposes the vulnerability

Credential stuffing is an automated attack where lists of breached username and password pairs are used to gain unauthorized access to accounts. In a NestJS application that uses Mutual TLS (mTLS), the presence of mTLS can create a false sense of security and inadvertently shift or amplify risk if authentication logic is misaligned with the transport guarantees.

Mutual TLS provides strong client authentication by requiring clients to present a valid certificate trusted by the server. When mTLS is enforced at the transport layer, an API endpoint might assume that a request with a valid client certificate is associated with a known, authenticated user. If the application then maps the certificate to a user identity but still relies on a session cookie or bearer token for authorization, an attacker can perform credential stuffing against that session or token endpoint. The attacker does not need to compromise the certificate; they only need a valid username/password pair to hijack an authenticated session or obtain a token once the certificate-based mapping is established.

Another exposure arises when endpoints that should require both mTLS and user credentials inadvertently relax checks for certain routes. For example, an endpoint that validates the client certificate but does not independently verify user credentials can be targeted by automated login attempts. Even though mTLS prevents unauthenticated clients from reaching the endpoint, credential stuffing can be effective against the application’s business logic if the server issues tokens or session cookies after validating user-supplied credentials, especially when rate limiting or lockout mechanisms are absent.

In NestJS, this often manifests when developers use guards or interceptors that check the certificate and then proceed to validate credentials in a way that does not adequately tie the two together. Attackers can probe the API with credential lists, leveraging the mTLS channel to bypass network-level IP or transport protections. Findings from scans such as middleBrick highlight these logic flaws by correlating authentication mechanisms with authorization outcomes, emphasizing that mTLS secures the channel but does not inherently prevent credential abuse at the application layer.

Mutual Tls-Specific Remediation in Nestjs — concrete code fixes

To securely combine mTLS with user authentication in NestJS, treat the client certificate as an identity signal rather than as the sole authorization mechanism. Always enforce user-level authentication and authorization after validating the certificate, and avoid implicit trust between the transport layer and application permissions.

Below are concrete code examples for enabling and using mTLS in NestJS with explicit user credential validation.

1. Enabling Mutual TLS in NestJS (HTTPS server options)

Configure the NestJS HTTPS adapter to require and validate client certificates. This example uses the built-in HTTPS server in NestJS with strict mTLS settings.

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as fs from 'fs';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    httpsOptions: {
      key: fs.readFileSync('path/to/server.key'),
      cert: fs.readFileSync('path/to/server.crt'),
      ca: [fs.readFileSync('path/to/ca.crt')],
      requestCert: true,    // require client certificate
      rejectUnauthorized: true, // reject clients with invalid/untrusted certs
    },
  });
  await app.listen(3000);
}
bootstrap();

2. Explicit certificate-to-user mapping with a custom guard

Do not rely solely on the presence of a valid certificate to authorize requests. Map the certificate subject or serial to a user, then require additional user credentials for sensitive actions.

// src/auth/cert-user.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ClientCertRequest } from 'tls';

@Injectable()
export class CertUserGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const cert = (request as any).client.verifiedCert; // populated by the HTTPS layer
    if (!cert) {
      return false;
    }
    const subject = cert.subject; // e.g., { CN: 'alice' }
    request.userFromCert = { username: subject.CN, fingerprint: cert.fingerprint };
    return true;
  }
}

// src/auth/local-user.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalUserGuard extends AuthGuard('local') {
  // Combines mTLS identity with explicit username/password credentials
  async canActivate(context: ExecutionContext) {
    await super.canActivate(context);
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    if (!user) return false;
    // Ensure a certificate-bound identity exists and matches or complements credentials
    return true;
  }
}

3. Securing endpoints with combined checks

Apply both guards and enforce per-request validation. For high-risk actions, require re-authentication of credentials even when a valid client certificate is present.

// src/users/users.controller.ts
import { Controller, Request, Post, Body, UseGuards } from '@nestjs/common';
import { CertUserGuard } from '../auth/cert-user.guard';
import { LocalUserGuard } from '../auth/local-user.guard';

@Controller('users')
export class UsersController {
  @UseGuards(CertUserGuard, LocalUserGuard)
  @Post('confirm')
  confirm(@Request() req, @Body('password') password: string) {
    // At this point, req.userFromCert is set by CertUserGuard
    // and req.user is set by LocalUserGuard after credential validation
    if (!req.user || req.user.fingerprint !== req.userFromCert.fingerprint) {
      throw new Error('Credential mismatch');
    }
    return { ok: true };
  }
}

4. Apply rate limiting and anomaly detection

Even with mTLS, implement rate limiting on authentication endpoints to mitigate credential stuffing attempts. This complements mTLS by preventing automated attempts from exhausting valid certificate-trusted sessions.

// src/rate-limit.config.ts
import { RateLimitConfig, RateLimitEnum } from '@nestjs/throttler';

export const rateLimitConfig: RateLimitConfig = {
  whitelist: [
    { path: '/users/confirm', method: 'POST', rate: RateLimitEnum.HIGH },
  ],
  globalLimit: {
    limit: 30,
    window: 60000,
  },
};

Frequently Asked Questions

Does mTLS alone prevent credential stuffing in NestJS?
No. Mutual TLS authenticates the client but does not validate user credentials. Credential stuffing can succeed if endpoints rely solely on mTLS and still issue tokens/sessions after validating a separate password. Always combine mTLS with explicit user authentication and authorization checks.
How can I test my NestJS API for these issues using middleBrick?
Run a scan with middleBrick to get per-category findings and prioritized guidance. middleBrick checks authentication boundaries and logic flaws, including scenarios where mTLS is present alongside credential-based flows, and maps findings to frameworks such as OWASP API Top 10.