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), orjti(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, orjticlaims; 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:
- Attempt to obtain a JWT via the login endpoint with a known credential pair (simulating a stuffing attempt).
- Replay that JWT after 30 seconds to test acceptance.
- Send a token with an expired
expclaim to confirm expiration enforcement. - Modify the
audclaim 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?
How can I verify that my JWT implementation rejects tampered tokens after a credential‑stuffing attempt?
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.