Api Rate Abuse in Adonisjs (Typescript)
Api Rate Abuse in Adonisjs with Typescript — how this combination creates or exposes the vulnerability
AdonisJS, a Node.js web framework for TypeScript, relies on route handlers and middleware to manage request flow. When rate limiting is misconfigured or omitted, API endpoints become susceptible to rate abuse, where attackers issue high volumes of requests to exhaust server resources, degrade performance, or bypass business logic constraints. Because AdonisJS encourages structured, type-safe code in TypeScript, developers may assume that route definitions and controller logic inherently protect against abuse. This is not the case; without explicit rate limiting, any authenticated or unauthenticated endpoint can be targeted.
Rate abuse manifests in several ways in AdonisJS applications written in TypeScript. For example, an authentication route that issues login tokens can be hammered with credential-stuffing attempts, or a resource-intensive query endpoint can be called repeatedly to trigger CPU or database load. AdonisJS does not enable global rate limiting by default; it requires developers to integrate packages such as @adonisjs/limiter or implement custom middleware. If these safeguards are skipped or improperly scoped—such as applying limits only to specific HTTP methods or failing to differentiate by user identity—an attacker can exploit unthrottled paths.
In TypeScript, strong typing can inadvertently obscure runtime behavior. A developer might define a controller method with strict parameter types, assuming validation includes rate considerations. However, validation libraries like Joi or Yup typically focus on payload structure, not request frequency. Without a dedicated rate-limiting layer, the type safety of TypeScript does not translate to protection against volumetric attacks. Furthermore, because AdonisJS supports dependency injection and modular architecture, developers might register limiter providers inconsistently across providers files, creating gaps where some routes remain unchecked.
Real-world attack patterns include burst requests that stay just below a threshold if limits are loosely defined, or distributed abuse across multiple IPs to evade simple counters. Common vulnerability patterns map to OWASP API Top 10 #2: Broken Object Level Authorization when rate limits are not applied per user or per role, enabling privilege escalation through repeated attempts. Inadequate rate limiting also intersects with data exposure risks, as repeated requests to list or export endpoints can lead to unauthorized data scraping.
middleBrick scans identify rate abuse risks by testing unauthentated and authenticated paths with parallel probes, checking for missing or inconsistent rate limiting across endpoints. It evaluates whether controls are applied at the correct layer, whether they differentiate based on identity or role, and whether they resist evasion techniques. The scanner aligns findings with frameworks like OWASP API Top 10 and provides prioritized remediation guidance, helping teams tighten controls specific to their AdonisJS + TypeScript implementation without relying on internal architecture disclosures.
Typescript-Specific Remediation in Adonisjs — concrete code fixes
Securing AdonisJS APIs written in TypeScript requires explicit, layered rate-limiting strategies combined with type-safe implementations. Below are concrete code examples demonstrating how to apply middleware-based rate limiting, differentiate limits by user roles, and integrate checks within an AdonisJS application.
- Install and configure the official limiter provider:
// In start/app.ts
import { application } from '@adonisjs/core'
import { cors } from '@adonisjs/cors'
import { limiter } from '@adonisjs/limiter'
export default application
.configure(limiter)
.configure(cors)
.registerProviders()
// In config/limiter.ts
import { defineConfig } from '@adonisjs/core'
export default defineConfig({
enabled: true,
adapters: {
memory: {
driver: 'memory',
max: 100,
refillRate: 10,
},
redis: {
driver: 'redis',
configKey: 'redis',
refillRate: 20,
max: 200,
},
},
limits: {
login: {
adapter: 'memory',
max: 5,
refillRate: 1,
refillInterval: 'minute',
},
default: {
adapter: 'redis',
max: 60,
refillRate: 10,
refillInterval: 'minute',
},
},
})
- Apply rate limiting selectively in routes with typed controller parameters:
// routes/api.ts
import Route from '@ioc:Adonis/Core/Route'
import { schema } from '@ioc:Adonis/Core/Validator'
Route.post('/login', async ({ request, response }) => {
// login logic
}).rateLimit({
adapter: 'memory',
max: 5,
refillRate: 1,
refillInterval: 'minute',
identifier: (ctx) => ctx.request.auth?.user?.id ?? ctx.request.ip(),
})
Route.get('/reports/:id', async ({ params, request }) => {
const id = params.id
// typed business logic
}).rateLimit({
adapter: 'redis',
max: 30,
refillRate: 5,
refillInterval: 'minute',
identifier: (ctx) => {
const user = ctx.request.auth?.user
if (user?.role === 'admin') return `admin:${user.id}`
return `user:${user?.id ?? ctx.request.ip()}`
},
})
- Create a custom middleware for role-based differentiation, preserving TypeScript typing:
// start/handlers/rateLimiter.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class RateLimiter {
protected limits = {
user: { max: 60, refillRate: 10, refillInterval: 'minute' },
premium: { max: 200, refillRate: 40, refillInterval: 'minute' },
admin: { max: 1000, refillRate: 200, refillInterval: 'minute' },
}
public async handle({ auth, response, request, logger }: HttpContextContract, next: () => Promise) {
const user = auth.user
const tier = user ? (user.role as 'user' | 'premium' | 'admin') : 'guest'
const config = this.limits[tier]
// Simple in-memory check for demonstration; in production use Redis or similar
const key = `${tier}:${user?.id ?? request.ip()}`
// Pseudo-code for limit check — integrate with a real store
const allowed = await this.checkLimit(key, config)
if (!allowed) {
response.status(429).send({ error: 'Too many requests' })
return
}
await next()
}
protected async checkLimit(key: string, config: any): Promise {
// Implementation depends on chosen adapter
return true
}
}
- Integrate the middleware globally or per-route in server middleware:
// start/kernel.ts
import rateLimiter from '#handlers/rateLimiter'
const kernel = new RouteKernel()
// Apply globally
kernel.global.add(rateLimiter)
// Or apply to specific routes in routes.ts
import Route from '@ioc:Adonis/Core/Route'
Route.group(() => {
Route.get('/admin/users', 'AdminController.index').use('rateLimiter')
}).prefix('api')
These examples demonstrate how TypeScript’s type system can be leveraged to enforce role-based rate limits while maintaining compile-time safety. By combining configuration-driven adapters, contextual identifiers, and middleware, developers can mitigate rate abuse without sacrificing the benefits of typed code.