HIGH credential stuffingkoamutual tls

Credential Stuffing in Koa with Mutual Tls

Credential Stuffing in Koa with Mutual TLS — how this specific combination creates or exposes the vulnerability

Credential stuffing is an automated attack where attackers use lists of breached username and password pairs to gain unauthorized access to accounts. In a Koa application that uses Mutual TLS (mTLS), the presence of client certificates adds a strong authentication factor, but it does not inherently prevent credential stuffing at the application layer. If the endpoint accepting client certificates also exposes a username/password login, or if mTLS is used only for some routes while others remain unprotected, the attack surface can persist.

Mutual TLS binds client identity to a certificate, but it does not rate-limit authentication attempts at the HTTP layer. An attacker who possesses valid client certificates can still perform credential stuffing against password-based endpoints, or probe account enumeration vulnerabilities (e.g., different responses for existing vs non-existing users). Moreover, if mTLS is misconfigured to accept any client certificate without validating it against a strict trust store, unauthorized clients might reach the application, bypassing intended access controls. The combination therefore creates a nuanced risk: mTLS secures transport and client identity, but application-level authentication logic must still defend against automated credential submission, session fixation, and user enumeration.

For example, a Koa route that verifies a client certificate and then reads a username from the request body can be targeted with a barrage of password guesses. If the route does not enforce rate limiting, account lockout, or CAPTCHA after repeated failures, the mTLS layer alone cannot stop the abuse. The scanner checks for missing rate limiting on authentication endpoints and flags cases where credentials are accepted despite the presence of mTLS, because the two controls address different parts of the threat model.

Mutual TLS-Specific Remediation in Koa — concrete code fixes

To securely combine Mutual TLS with resilient authentication in Koa, enforce strict client certificate validation, apply rate limiting, and avoid leaking account existence. Below are concrete, working examples that demonstrate a hardened setup.

1. Configure mTLS in Koa with explicit trust store and strict verification

Ensure the server requests and validates client certificates against a known CA, and reject connections where the certificate is missing or invalid.

const Koa = require('koa');
const https = require('https');
const fs = require('fs');

const app = new Koa();

const serverOptions = {
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
  ca: fs.readFileSync('ca-cert.pem'),
  requestCert: true,
  rejectUnauthorized: true,
};

https.createServer(serverOptions, app.callback()).listen(8443, () => {
  console.log('Koa mTLS server listening on https://localhost:8443');
});

2. Extract and validate client certificate details in middleware

After mTLS handshake, inspect the client certificate to enforce additional constraints (e.g., allowed subjects or serial numbers) and tie certificate identity to application identity safely.

app.use(async (ctx, next) => {
  const cert = ctx.req.client.verifiedCert;
  if (!cert) {
    ctx.status = 403;
    ctx.body = { error: 'Client certificate required' };
    return;
  }
  // Example: restrict by certificate subject or SAN
  const allowedSerials = new Set(['AB:CD:EF:12:34:56']);
  if (!allowedSerials.has(cert.serialNumber)) {
    ctx.status = 403;
    ctx.body = { error: 'Certificate not authorized' };
    return;
  }
  ctx.assert(cert.subject, 400, 'Certificate subject missing');
  await next();
});

3. Apply layered defenses on authentication endpoints

Use rate limiting and careful response handling to mitigate credential stuffing regardless of mTLS presence. This prevents attackers from leveraging mTLS-authenticated sessions to brute-force passwords.

const Router = require('koa-router');
const rateLimit = require('koa-rate-limit');

const authRouter = new Router();

// Rate limit login attempts per client certificate identity or IP
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // max 5 attempts per window
  keyGenerator: (ctx) => {
    // Use certificate serial or IP as key
    return ctx.req.client.verifiedCert?.serialNumber || ctx.ip;
  },
  handler: (ctx) => {
    ctx.status = 429;
    ctx.body = { error: 'Too many attempts, try again later' };
  },
});

authRouter.post('/login', loginLimiter, async (ctx) => {
  const { username, password } = ctx.request.body;
  // Perform secure password verification (e.g., bcrypt.compare)
  // Avoid leaking whether username exists
  const user = await getUserByUsername(username);
  const validPassword = user && await bcrypt.compare(password, user.passwordHash);
  if (!validPassword) {
    ctx.status = 401;
    ctx.body = { error: 'Invalid credentials' };
    return;
  }
  // Issue session/token
  ctx.body = { token: 'secure-session-token' };
});

app.use(authRouter.routes()).use(authRouter.allowedMethods());

4. Defend against user enumeration and ensure safe responses

Return consistent responses for authentication failures and avoid information disclosure that could aid attackers. Combine this with mTLS-bound user records for stronger assurance.

async function getUserByUsername(username) {
  // Fetch user record tied to mTLS subject if possible
  return db.users.findOne({ username });
}

Frequently Asked Questions

Does Mutual TLS alone prevent credential stuffing attacks?
No. Mutual TLS authenticates the client at the transport layer but does not protect application-level password endpoints. Without rate limiting, secure password storage, and careful response handling, credential stuffing can still succeed against login routes.
How can I verify that my Koa server enforces strict mTLS and rejects unauthorized clients?
Use a test client that omits a certificate or presents an untrusted certificate and confirm the server rejects the connection with a TLS error. Inspect request.client.verifiedCert in middleware and ensure rejectUnauthorized is set to true in the HTTPS server options.