Dictionary Attack in Adonisjs with Jwt Tokens
Dictionary Attack in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A dictionary attack in AdonisJS when JWT tokens are used typically targets the authentication endpoint that verifies user credentials and issues a JWT. If the login route does not enforce adequate rate limiting or account lockout, an attacker can submit many username and password guesses per minute. AdonisJS applications that rely solely on JWT for session management may not inherently throttle login attempts because the server does not store session state.
Without per‑user or per‑IP rate controls, each request is stateless and can be retried with different credentials at high speed. Attackers often use a list of common passwords or breached credential pairs, iterating over known usernames (e.g., email addresses collected from prior breaches) or even usernames derived from application logic (e.g., numeric IDs or usernames exposed in URLs or logs).
If password hashing is weak (e.g., missing or low‑cost bcrypt rounds), offline cracking becomes easier once token signing keys are not properly protected. JWT tokens themselves are not the direct target of a dictionary attack on login, but weak passwords combined with unrestricted login endpoints allow attackers to obtain valid JWTs. These tokens can then be used to access protected API routes until they expire.
Additional risk occurs when JWTs embed user identifiers in claims; if an attacker discovers predictable identifiers (sequential numeric IDs) and the application does not enforce proper ownership checks (BOLA/IDOR), they might attempt to leverage a dictionary attack not on passwords but on token usage patterns. For example, iterating over likely user IDs while using a stolen or guessed token to probe authorization boundaries.
Common insecure implementations include omitting middleware that tracks failed attempts, not using exponential backoff on the client side, and failing to lock accounts after repeated failures. Logging and monitoring gaps also prevent early detection, allowing attackers to run sustained campaigns without triggering alerts.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
To secure JWT-based authentication in AdonisJS against dictionary attacks, implement layered controls: rate limiting on the login route, strong password hashing, and proper token handling. Below are concrete code examples.
1. Rate limiting on the login route using AdonisJS middleware:
// start/handlers/rateLimitLogin.ts
import { Exception } from '@poppinss/utils'
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class RateLimitLogin {
public static config = {
rateLimit: {
maxAttempts: 5,
windowMs: 60_000, // 1 minute
keyGenerator( { auth }: HttpContextContract) {
// key by IP; consider combining with username for stricter control
return auth.use('api').user?.identifier || auth.ctx.request.ip
}
}
}
public async handle(ctx: HttpContextContract, next: () => Promise) {
const rateLimiter = use('Adonis/Addons/RateLimiter')
await rateLimiter.check(RateLimitLogin.config, 'login')
await next()
}
}
Register the middleware in start/kernel.ts and apply it to the login route:
// start/routes.ts
import Route from '@ioc:Adonis/Core/Route'
import RateLimitLogin from './handlers/rateLimitLogin'
Route.post('login', 'AuthController.login').middleware([RateLimitLogin])
2. Strong password hashing and token issuance:
// app/Controllers/Http/AuthController.ts
import { schema } from '@ioc:Adonis/Core/Validator'
import Hash from '@ioc:Adonis/Core/Hash'
import { DateTime } from 'luxon'
import { Jwt } from 'jose'
export default class AuthController {
public async login({ request, auth, response }: HttpContextContract) {
const bodySchema = schema.create({
username: schema.string.optional([ rules.email() ]),
password: schema.string()
})
const payload = await request.validate({ schema: bodySchema })
const user = await User.findBy('email', payload.username)
if (!user || !(await Hash.verify(user.password, payload.password))) {
throw new Exception('Invalid credentials', 401, 'invalid_credentials')
}
// Issue JWT with reasonable expiration
const secret = new TextEncoder().encode(env.get('JWT_SECRET'))
const token = await new Jwt({
alg: 'HS256',
typ: 'JWT'
}).setProtectedHeader({ alg: 'HS256' })
.setIssuer('adonisjs-app')
.setAudience('api')
.setExpirationTime('2h')
.setIssuedAt()
.setSubject(user.id.toString())
.sign(secret)
return response.ok({ token, expiresIn: '2h' })
}
}
3. Enforce secure token usage and validation on protected routes to avoid token misuse:
// app/Helpers/validateJwt.ts
import { JwtPayload, verify } from 'jose'
export function validateJwt(token: string) {
const secret = new TextEncoder().encode(env.get('JWT_SECRET'))
try {
const payload = verify(token, secret) as JwtPayload
if (typeof payload.sub !== 'string') {
throw new Error('invalid_subject')
}
return { valid: true, payload }
} catch (err) {
return { valid: false, error: err instanceof Error ? err.message : 'unknown' }
}
}
Use this helper in route middleware to reject malformed or expired tokens and to ensure claims like iss and aud match expectations, reducing the impact of leaked tokens.