HIGH credential stuffingjwt tokens

Credential Stuffing with Jwt Tokens

How Credential Stuffing Manifests in Jwt Tokens

Credential stuffing is the automated use of leaked username‑password pairs to gain unauthorized access. When an API issues JSON Web Tokens (JWT) as part of its authentication flow, a successful stuffing attempt yields a valid token that the attacker can then reuse against protected endpoints. The attack surface therefore includes:

  • Login/token endpoint abuse – attackers send high‑volume POST requests to /auth/login or /oauth/token with credential lists. Each successful response returns a signed JWT (access token, often with a refresh token).
  • Token replay via stolen JWTs – if the API does not enforce token revocation or unique identifiers, a token obtained from one stuffed credential can be reused indefinitely until it expires.
  • Weak token claims – missing or lax validation of iss (issuer), aud (audience), exp (expiration), or jti (JWT ID) allows attackers to forge or reuse tokens after credential stuffing succeeds.

Real‑world examples illustrate the impact. In 2020, a credential‑stuffing campaign against a major video‑streaming service (CVE‑2020‑13757 is unrelated but shows JWT misuse) harvested thousands of accounts by repeatedly posting to the login API and abusing the returned JWTs to stream premium content. The OWASP API Security Top 10 2023 lists this under API1:2023 Broken Authentication, noting that credential stuffing is a prevalent technique when authentication lacks rate limiting, strong password policies, or multi‑factor authentication.

Jwt Tokens-Specific Detection

Detecting credential‑stuffing‑related JWT weaknesses involves checking both the authentication endpoint and the token handling logic. middleBrick’s black‑box scan looks for the following indicators:

  • Missing or insufficient rate limiting on the login/token endpoint – the scanner sends bursts of requests and measures response times or status codes.
  • Absence of token revocation checks** – by presenting a previously issued JWT after a simulated logout, the scanner verifies whether the API still accepts the token.
  • Weak JWT validation** – missing verification of iss, aud, exp, or jti claims; the scanner crafts tokens with altered claims to see if they are accepted.
  • Long‑lived access tokens** – tokens with expiration far beyond typical session lengths (e.g., >1 hour) increase the reuse window for stolen credentials.

For example, when scanning a Node.js service that uses the jsonwebtoken library, middleBrick will:

  1. Attempt to obtain a JWT via the login endpoint with a known credential pair (simulating a stuffing attempt).
  2. Replay that JWT after 30 seconds to test acceptance.
  3. Send a token with an expired exp claim to confirm expiration enforcement.
  4. Modify the aud claim and verify rejection.

Findings are reported with severity, remediation guidance, and a mapping to OWASP API1:2023. The scan completes in 5–15 seconds, requires no agents or credentials, and outputs a JSON report that can be consumed by the middleBrick CLI, GitHub Action, or MCP Server.

Jwt Tokens-Specific Remediation

Mitigating credential stuffing in JWT‑based APIs focuses on hardening the authentication flow and enforcing strict token validation. Below are concrete, language‑specific fixes using widely adopted libraries.

1. Rate‑limit the login endpoint

Node.js (Express) example using express-rate-limit:

const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 10,                  // limit each IP to 10 login requests per window
  standardHeaders: true,
  legacyHeaders: false,
});

app.post('/auth/login', loginLimiter, (req, res) => {
  // existing login logic that issues a JWT
});

This throttles credential‑stuffing attempts before they can generate many JWTs.

2. Issue short‑lived access tokens and use refresh‑token rotation

Python (PyJWT) example:

import jwt, datetime, uuid

ACCESS_TOKEN_EXPIRY = datetime.timedelta(minutes=15)
REFRESH_TOKEN_EXPIRY = datetime.timedelta(days=14)

def create_access_token(user_id):
    payload = {
        'sub': str(user_id),
        'iat': datetime.datetime.utcnow(),
        'exp': datetime.datetime.utcnow() + ACCESS_TOKEN_EXPIRY,
        'jti': str(uuid.uuid4()),   # unique identifier for revocation
        'iss': 'myapi.example.com',
        'aud': 'myapi-clients'
    }
    return jwt.encode(payload, PRIVATE_KEY, algorithm='RS256')

def create_refresh_token(user_id):
    payload = {
        'sub': str(user_id),
        'iat': datetime.datetime.utcnow(),
        'exp': datetime.datetime.utcnow() + REFRESH_TOKEN_EXPIRY,
        'jti': str(uuid.uuid4())
    }
    return jwt.encode(payload, REFRESH_SECRET, algorithm='HS256')

The short expiry limits the usefulness of any token obtained via credential stuffing. The jti claim enables a server‑side revocation list (e.g., stored in Redis) to invalidate tokens immediately after logout or password change.

3. Validate all standard JWT claims

Middleware that rejects tokens with missing or invalid claims:

const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const auth = req.headers['authorization'];
  if (!auth || !auth.startsWith('Bearer ')) return res.sendStatus(401);
  const token = auth.split(' ')[1];

  try {
    const payload = jwt.verify(token, PUBLIC_KEY, {
      issuer: 'myapi.example.com',
      audience: 'myapi-clients',
      algorithms: ['RS256']
    });
    // optional: check jti against a revocation store
    if (isTokenRevoked(payload.jti)) {
      return res.sendStatus(401);
    }
    req.user = payload;
    next();
  } catch (err) {
    return res.sendStatus(401);
  }
}

function isTokenRevoked(jti) {
  // lookup jti in Redis; return true if revoked
}

By enforcing iss, aud, exp, and jti, the API ensures that even a token obtained via credential stuffing cannot be reused after a short window or after explicit revocation.

4. Additional defensive measures

  • Require multi‑factor authentication (MFA) for high‑risk actions.
  • Monitor for abnormal spikes in successful logins per IP or account and trigger alerts or temporary locks.
  • Hash passwords with a strong, adaptive algorithm (e.g., Argon2id) and enforce password‑strength policies.

Applying these controls reduces the likelihood that credential stuffing yields a usable JWT and limits the damage if a token is nevertheless obtained.

Frequently Asked Questions

Does middleBrick test for credential stuffing directly?
middleBrick does not launch credential‑stuffing attacks itself. Instead, it scans the authentication endpoint for missing rate‑limiting, insufficient token expiration, and absent JWT claim validation—conditions that enable credential stuffing to succeed. The findings are reported with guidance on how to add those defenses.
How can I verify that my JWT implementation rejects tampered tokens after a credential‑stuffing attempt?
Deploy a test that sends a valid JWT obtained from a legitimate login, then modifies one of the standard claims (e.g., changes the aud or flips a bit in the signature). A properly configured API will return a 401 Unauthorized response. middleBrick’s active probes perform similar claim‑tampering checks and report any acceptance as a finding.