HIGH broken authenticationadonisjsmutual tls

Broken Authentication in Adonisjs with Mutual Tls

Broken Authentication in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability

Mutual Transport Layer Security (mTLS) requires both the client and the server to present valid certificates. In Adonisjs, mTLS is typically enforced at the reverse proxy or load balancer (e.g., Nginx/HAProxy) or via a custom HTTPS server setup using Node.js TLS options. When mTLS is implemented but authentication logic is not rigorously enforced at the application layer, the combination can create a false sense of security and expose authentication weaknesses.

One common pattern is to rely solely on mTLS client certificate verification to identify users, assuming the certificate subject or serial maps directly to a user account. If Adonisjs does not independently validate that the certificate maps to an active, authorized user in its own identity store, attackers can exploit shared or leaked certificates, or certificates issued for unrelated roles, to assume identities they should not have. This is a Broken Authentication issue (OWASP API Top 10:2023 | A07:2023 – Identification and Authentication Failures).

Additionally, if session management or token issuance is not tightly coupled with mTLS verification, there is a risk of authentication bypass. For example, an application might accept a valid mTLS certificate but then rely on weak session cookies or missing authorization checks for subsequent endpoints. An attacker who compromises a client certificate can move laterally if the backend does not enforce per-request authorization (BOLA/IDOR). The vulnerability is not in mTLS itself but in how Adonisjs integrates certificate identity with application authentication and authorization.

Consider an Adonisjs API that uses mTLS to terminate TLS at a proxy and then passes an X-SSL-Client-Cert header with the raw certificate. If the route handlers trust this header without verifying the certificate chain, signature validity, and revocation status, they effectively accept any client that can inject that header. This can occur in development or misconfigured staging environments where TLS is terminated early and internal traffic is considered trusted.

Real attack patterns include:

  • Using a stolen or shared client certificate to authenticate as another user (authentication confusion).
  • Exploiting missing authorization checks after mTLS authentication to access other users’ resources via BOLA (e.g., /users/123/profile vs /users/456/profile).
  • Leveraging verbose error messages from Adonisjs (e.g., during certificate parsing) to learn about valid certificate subjects or internal user mappings.

To detect this with middleBrick, scans test unauthenticated endpoints and inspect whether mTLS is enforced and whether authentication checks are independent of transport-layer indicators. The LLM/AI Security checks do not apply here, but the scanner’s Authentication, Authorization, and Input Validation checks will highlight missing or weak application-level identity verification despite mTLS presence.

Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on ensuring that mTLS client identity is explicitly mapped and verified within Adonisjs, and that authorization checks are applied independently. Do not rely on headers injected by proxies without strict validation; instead, use the TLS socket details when running Node.js HTTPS servers, or validate proxy headers against a strict allowlist.

Example 1: Custom HTTPS server in Adonisjs with mTLS verification using Node.js tls module. This example creates an HTTPS server that requires client certificates and maps the certificate subject to a user record in the database. It does not trust proxy headers for authentication.

const fs = require('fs');
const https = require('https');
const { Ignitor } = require('@adonisjs/ignitor');

const serverOptions = {
  key: fs.readFileSync('path/to/server.key'),
  cert: fs.readFileSync('path/to/server.crt'),
  ca: fs.readFileSync('path/to/ca.crt'),
  requestCert: true,
  rejectUnauthorized: true, // Enforce client certificate validation
};

https.createServer(serverOptions, async (req, res) =>
{
  // req.client.verified will be true only if client cert validated by CA
  if (!req.client.verified) {
    res.writeHead(401);
    res.end('Unauthorized');
    return;
  }

  // Extract certificate subject; example: CN=user-123,OU=auth
  const rawCert = req.client.authorizedCertificate;
  // PEM to subject parsing — in production, use a robust library
  const subject = rawCert.subject; // raw object with C, ST, O, CN, etc.
  const commonName = subject.CN; // e.g., "user-123"

  // Map CN to a user in your Adonisjs app (pseudocode)
  const user = await User.findBy('certificate_cn', commonName);
  if (!user || !user.isActive) {
    res.writeHead(403);
    res.end('Forbidden: certificate not mapped to active user');
    return;
  }

  // Attach user to request for downstream handlers
  req.user = user;

  // Continue to Adonisjs request lifecycle via adapter (framework-specific)
  // For illustration, we respond directly:
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: `Authenticated as ${user.id}` }));
}).listen(8443);

Example 2: If you terminate TLS at a proxy and must rely on headers, validate the header against the certificate presented to the proxy and enforce strict allowlist checks. Do not use raw header values as the source of identity.

// Example middleware in Adonisjs route handler
Route.get('/profile', async ({ request, response }) =>
{
  const tlsCertHeader = request.header('x-client-cert');
  const tlsVerified = request.header('x-client-verify'); // set by proxy to SUCCESS

  // Basic validation — in production, parse and validate certificate chain
  if (tlsVerified !== 'SUCCESS' || !tlsCertHeader) {
    return response.status(401).json({ error: 'mTLS required' });
  }

  // Map header to user via a strict, server-side mapping (e.g., database)
  const user = await User.findBy('proxy_cert_fingerprint', tlsCertHeader);
  if (!user) {
    return response.status(403).json({ error: 'Unauthorized certificate' });
  }

  request.user = user;
  // Proceed with route logic
});

Key remediation practices:

  • Always set rejectUnauthorized: true on the server side to enforce client certificates.
  • Map certificate identity (subject, serial, or fingerprint) to application users explicitly; do not assume trust based on TLS alone.
  • Apply the same authorization checks (e.g., BOLA guards) regardless of mTLS presence.
  • Do not trust injected headers (e.g., X-SSL-Client-Cert) unless you control the full chain and validate the certificate against your CA store.

middleBrick can help by scanning your endpoints to confirm that mTLS is enforced and that application-level authentication does not rely solely on transport-layer indicators. The dashboard and CLI allow you to track these findings and integrate checks into CI/CD pipelines.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Can mTLS alone prevent Broken Authentication in Adonisjs?
No. mTLS ensures the client is who they claim to be at the transport layer, but Adonisjs must still map certificate identity to application users and enforce authorization checks. Relying only on mTLS can lead to authentication confusion if certificates are shared or leaked.
How does middleBrick detect Broken Authentication issues when mTLS is in use?
middleBrick tests unauthenticated attack surfaces and checks whether authentication and authorization logic are independent of TLS assumptions. It does not test internal proxy configurations but flags missing application-level identity verification and over-reliance on headers or certificate subject alone.