Api Rate Abuse in Adonisjs with Jwt Tokens
Api Rate Abuse in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
AdonisJS applications that rely exclusively on JWT tokens for authentication can still be vulnerable to API rate abuse. While JWTs provide stateless identity and eliminate the need for server-side session storage, they do not inherently enforce usage limits. A client with a valid token can make repeated requests at high volume, consuming server resources and potentially degrading availability for other users.
Because JWTs are self-contained and typically accepted without repeated database lookups, common request-level protections that rely on user ID or session state may be bypassed or applied inconsistently. For example, if rate limiting is implemented only against IP addresses, an attacker can rotate IPs while using the same token, or use a pool of compromised tokens issued to different accounts. If limits are applied per token but the token payload lacks a stable identifier, the implementation may miscount requests or fail to group them correctly.
Attack patterns enabled by this combination include token sharing among colluding clients, where a limited token is distributed across multiple clients, and token replay from compromised logs or browser storage. Inadequate enforcement windows and counters can allow burst traffic that overwhelms routes such as authentication endpoints, password reset flows, or high-cost data export APIs. Because the API is unauthenticated from the scanner’s perspective during black-box testing, middleBrick can detect missing or weak rate controls and highlight them as findings in the Rate Limiting category.
Real-world examples align with the OWASP API Top 10, specifically the Broken Object Level Authorization and Rate Limiting categories. For instance, an endpoint like /api/v1/users/:id/profile that accepts a JWT in the Authorization header may lack token-aware throttling, enabling an attacker to iterate over user IDs while staying within per-IP limits. Similarly, a token-based password reset route without request caps can be targeted via email enumeration or brute-force attempts.
middleBrick’s unauthenticated scan exercises these routes with and without tokens, comparing behavior and identifying gaps in enforcement scope. Findings include missing token identifiers in rate limit keys, inconsistent window sizes across endpoints, and missing response headers that would inform clients of quota status. The scanner validates whether limits apply at the token level, per user claims, or globally, and surfaces these as actionable items in the report.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on making rate limiting token-aware while preserving the stateless benefits of JWTs. You should derive a stable request key from the token payload, such as a subject (sub) or a custom claim like account_id, and apply limits scoped to that key. This prevents token sharing abuse and ensures that each principal is counted correctly regardless of IP rotation.
In AdonisJS, you can implement this using a combination of route middleware and a Redis-based counter. Below is a complete, syntactically correct example that reads the JWT, extracts the subject, and applies a sliding window limit per subject.
// start/handlers/rate_limit_jwt.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { RateLimiterRedis } from 'rate-limiter-flexible'
import { createClient } from 'redis'
const redisClient = createClient({
host: process.env.REDIS_HOST || '127.0.0.1',
port: Number(process.env.REDIS_PORT) || 6379,
})
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'rl_jwt_subject',
points: 100, // 100 requests
duration: 60, // per 60 seconds
blockDuration: 300, // block for 5 minutes if exceeded
})
export default class RateLimitJwt {
public async handle({ request, response, auth }: HttpContextContract, next: () => Promise) {
const token = request.headers()['authorization']?.replace('Bearer ', '')
if (!token) {
return response.unauthorized('Missing token')
}
let payload: any
try {
// Use your JWT verification library; this example uses jws-like API
payload = verifyJwt(token, process.env.JWT_SECRET as string)
} catch (error) {
return response.unauthorized('Invalid token')
}
// Ensure a stable key exists in the token
const subject = payload.sub || payload.user_id
if (!subject) {
return response.badRequest('Token missing subject claim')
}
try {
await rateLimiter.consume(subject)
} catch (rateLimitError) {
const retryAfter = rateLimitError.msBeforeNext / 1000
response.header('Retry-After', String(Math.ceil(retryAfter)))
return response.status(429).json({ error: 'Too Many Requests' })
}
await next()
}
}
Register this middleware in start/kernel.ts and apply it selectively to sensitive routes, such as authentication and high-cost endpoints.
// start/kernel.ts
import Route from '@ioc:Adonis/Core/Route'
import RateLimitJwt from 'App/Handlers/rate_limit_jwt'
Route.group(() => {
Route.post('auth/login', 'AuthController.login').middleware([RateLimitJwt])
Route.post('auth/reset-password', 'AuthController.resetPassword').middleware([RateLimitJwt])
Route.get('api/v1/users/:id/profile', 'ProfileController.show').middleware([RateLimitJwt])
}).prefix('api/v1')
For token-bound limits, ensure your JWT includes a sub or similar stable identifier. If you issue tokens with opaque references, map them to a subject server-side before applying limits. You can also layer limits by combining token scope with IP-based throttling for defense in depth, but prioritize token-based enforcement to avoid bypass via IP rotation.
middleBrick’s scans validate whether these token-aware limits are present and whether they cover high-risk endpoints. The tool checks for the presence of identifiers in the token, verifies that limits are applied per token or subject, and flags routes where burst traffic could exhaust server-side resources without detection.