Api Rate Abuse in Express with Jwt Tokens
Api Rate Abuse in Express with Jwt Tokens — how this combination creates or exposes the vulnerability
Rate abuse in an Express API that uses JWT tokens can occur when protections are applied inconsistently across authenticated and unauthenticated paths. JWTs typically carry a subject (sub) and possibly roles or scopes, which developers may use to relax rate limits for recognized users. If limits are enforced only after token validation and not on the raw request surface, an attacker can consume unauthenticated entry points to exhaust per-identity or per-IP quotas before tokens are checked, or they can intentionally trigger token validation failures to bypass intended caps.
Consider an Express route that decodes a JWT but does not enforce a rate limit on the incoming request keyed by the token’s subject. The request path may still hit a global middleware limit based on IP, but if the token is validated later in the handler chain, the per-subject quota is not applied early. This gap allows an attacker to generate many cheap, unauthenticated requests that each carry a different or missing token, effectively abusing the absence of early subject-based throttling. In another scenario, a token with elevated scopes might be presented for an endpoint that should still be bounded by a lower-tier quota, leading to privilege-enabled rate abuse where a single token can make a high volume of calls that exceed business limits.
These patterns map to the BFLA/Privilege Escalation and Rate Limiting checks in middleBrick’s scans. For example, a scan might flag an endpoint where token validation occurs after business logic or where per-subject limits are missing, while also showing that unauthenticated paths lack independent caps. Attack patterns such as token enumeration or substitution can be chained with high-volume bursts to probe for weak or inconsistent enforcement. Because JWTs are often assumed to identify a trusted caller, developers may overlook that tokens can be obtained anonymously (e.g., public endpoints that issue tokens without strict checks), which further widens the attack surface for rate abuse.
To detect these issues, middleBrick tests whether rate limiting is applied before sensitive authorization decisions and whether token scopes align with expected quotas. Findings typically highlight missing per-subject limits on authenticated routes, missing token validation on high-risk methods, and inconsistent enforcement across public and protected paths. Remediation focuses on normalizing rate enforcement across all entry points, binding limits to token subjects or scopes early, and ensuring that unauthenticated paths have appropriate caps independent of downstream identity checks.
Jwt Tokens-Specific Remediation in Express — concrete code fixes
Apply rate limits early and consistently, using a key that incorporates the JWT subject when the token is valid and falling back to IP-based limits when it is not. Below is a concrete Express example that demonstrates these patterns with real JWT handling code.
import express from 'express';
import jwt from 'jsonwebtoken';
import rateLimit from 'express-rate-limit';
const app = express();
const PUBLIC_ROUTES = ['/login', '/token'];
// Base limiter for unauthenticated paths
const unauthLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
keyGenerator: (req) => {
// Prefer a known identifier, otherwise IP
if (req.verifiedSubject) return `subject:${req.verifiedSubject}`;
return req.ip;
},
skip: (req) => PUBLIC_ROUTES.includes(req.path),
});
// Stricter limiter for authenticated requests per subject
const authLimiter = rateLimit({
windowMs: 60 * 1000,
max: 1000,
keyGenerator: (req) => `subject:${req.verifiedSubject}`,
skip: (req) => !req.verifiedSubject,
});
// JWT verification middleware that attaches subject to request
const jwtCheck = (req, res, next) => {
const auth = req.headers.authorization || '';
const match = auth.match(/Bearer\s(\S+)/);
const token = match ? match[1] : null;
if (!token) { req.verifiedSubject = null; return next(); }
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.verifiedSubject = payload.sub || payload.userId;
} catch (err) {
req.verifiedSubject = null;
}
next();
};
app.use(unauthLimiter);
app.use(jwtCheck);
app.use(authLimiter);
// Example protected route where limits are enforced before business logic
app.get('/api/profile', (req, res) => {
if (!req.verifiedSubject) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json({ subject: req.verifiedSubject, data: 'profile' });
});
app.listen(3000, () => console.log('Server running on port 3000'));
Key points in this setup:
- The unauth limiter applies before token validation and uses a fallback key (IP) when no subject is available, ensuring unauthentinated paths are still bounded.
- After JWT verification, the subject is attached to the request, enabling the auth limiter to enforce per-subject quotas consistently.
- By skipping the auth limiter when there is no verified subject, we avoid misapplying stricter limits to public endpoints while still protecting them with the unauth limiter.
- The keyGenerator centralizes how the rate limit key is derived, making it straightforward to incorporate roles or scopes into the key if needed (for example, `subject:${req.verifiedSubject}:${req.roles}`).
Complementary practices include validating token presence and scope early in the middleware chain, avoiding late token checks, and ensuring that token issuance does not bypass rate policies. middleBrick’s scans can verify that these controls are present by checking whether per-subject limits are defined and whether unauthenticated endpoints have independent caps, producing findings mapped to Rate Limiting and BFLA categories with remediation guidance.