MEDIUM clickjackingnestjsmutual tls

Clickjacking in Nestjs with Mutual Tls

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

Clickjacking is a client-side UI redress attack where an invisible or misleading layer tricks a user into interacting with a different page. In a NestJS application protected by Mutual TLS (mTLS), the presence of strong transport-layer authentication does not prevent clickjacking. mTLS ensures that the client presenting a valid certificate is authenticated to the server, but it does not enforce how the authenticated response is rendered or embedded by browsers. An authenticated user’s session can still be hijacked via an embedded frame if the server does not set appropriate frame-ancestors or X-Frame-Options headers.

When mTLS is used, the server typically terminates the TLS connection at the ingress (e.g., a load balancer or API gateway) and forwards the authenticated identity to NestJS via headers such as x-forwarded-cert or a custom header containing the client certificate details. If NestJS relies solely on this identity for authorization and does not apply response-level protections, an attacker can craft a page that loads the authenticated endpoint inside an iframe. Because the browser will include cookies and the mTLS-derived identity for same-site origins, the forged UI can perform actions on behalf of the user. The combination of mTLS and missing frame-protection creates a scenario where strong authentication coexists with a vulnerable UI surface.

Common misconfigurations that exacerbate the issue include:

  • Not setting X-Frame-Options or Content-Security-Policy frame-ancestors, allowing the page to be embedded anywhere.
  • Assuming mTLS alone prevents embedding, which is incorrect because framing is controlled by HTTP response headers, not transport identity.
  • Exposing sensitive UI routes (e.g., confirmation or transaction endpoints) without validating the Origin or Referer header in addition to mTLS client checks.

For example, an endpoint that returns a banking transfer form and is protected by mTLS could be loaded inside an attacker’s page. The browser will send the client certificate’s associated session context if cookies are present, enabling unauthorized actions unless the server explicitly disallows framing.

Mutual Tls-Specific Remediation in Nestjs — concrete code fixes

Remediation focuses on enforcing that pages are not embedded in frames and that mTLS-derived identities are treated as part of a broader defense-in-depth strategy. You should combine transport-layer mTLS with response headers that prevent framing. Below are concrete NestJS examples.

1. Enforce frame-ancestors via middleware or built-in Helmet configuration. Helmet’s frameguard sets X-Frame-Options and Content-Security-Policy headers. Ensure your NestJS app applies Helmet correctly.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Use helmet to set security headers, including frameguard
  app.use(helmet.frameguard({ action: 'deny' })); // or 'sameorigin' if embedding is required
  await app.listen(3000);
}
bootstrap();

2. Apply CSP frame-ancestors explicitly for stricter control. This is useful if you need to allow embedding only from specific origins while still using mTLS for authentication.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(helmet.contentSecurityPolicy({
    useDefaults: true,
    directives: {
      ...helmet.contentSecurityPolicy.getDefaultDirectives(),
      'frame-ancestors': ["'self'", 'https://trusted.example.com'],
    },
  }));
  await app.listen(3000);
}
bootstrap();

3. Validate Origin/Referer in addition to mTLS client certificates for state-changing requests. Treat mTLS as a strong identity proof, but still enforce same-site or cross-origin checks at the application layer for sensitive operations.

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { request } from 'http';

@Injectable()
export class FrameValidationInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const request = context.switchToHttp().getRequest();
    const allowedOrigin = 'https://your-frontend.example.com';
    const requestOrigin = request.headers.origin;
    const requestReferer = request.headers.referer;

    if (request.method === 'POST' || request.method === 'PUT' || request.method === 'DELETE') {
      if (!requestOrigin || requestOrigin !== allowedOrigin) {
        throw new Error('Invalid origin');
      }
      // Optionally also check referer header for additional assurance
      if (!requestReferer || !requestReferer.startsWith(allowedOrigin)) {
        throw new Error('Invalid referer');
      }
    }
    return next.handle();
  }
}

4. Combine these measures with mTLS verification. Ensure your NestJS app can access the client certificate details (e.g., via x-forwarded-cert) and use them for audit logging, but do not rely on them for framing decisions.

// Example of extracting client certificate from headers set by your ingress
app.use((req, res, next) => {
  const clientCert = req.headers['x-forwarded-cert'];
  if (!clientCert) {
    res.status(400).send('mTLS certificate required');
    return;
  }
  // Perform additional validation (e.g., check thumbprint against allowed list)
  next();
});

These steps ensure that even when mTLS strongly authenticates clients, the application explicitly prevents framing, reducing the risk of clickjacking.

Frequently Asked Questions

Does mTLS alone prevent clickjacking attacks?
No. mTLS authenticates the client at the transport layer but does not control how pages are rendered or embedded. Without frame-ancestors or X-Frame-Options headers, authenticated pages can still be embedded in malicious frames, enabling clickjacking.
Can the middleBrick dashboard help identify missing security headers like frame-ancestors?
Yes. The middleBrick dashboard includes checks for missing or weak framing protections. By scanning your endpoints, it can flag missing Content-Security-Policy frame-ancestors or absent X-Frame-Options, helping you enforce secure embedding policies alongside mTLS.