Brute Force Attack in Adonisjs with Jwt Tokens
Brute Force Attack in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A brute force attack against an API using JWT tokens in AdonisJS typically targets the token validation or authentication endpoints rather than attempting to crack the token signature itself. Because JWTs are cryptographically signed, attackers cannot feasibly guess or forge a valid token. Instead, they use high-volume automated requests to guess valid user identities (e.g., numeric IDs or known usernames/emails) or use credential stuffing techniques to discover accounts with weak passwords. In AdonisJS, if authentication routes do not enforce strict rate limiting, each request consumes server-side resources for token verification, signature validation, and database lookups. This can lead to resource exhaustion, increased latency for legitimate users, and potential account enumeration when responses differ between existing and non-existing accounts.
The vulnerability is amplified when applications expose token introspection or validation endpoints without proper controls. For example, an endpoint that returns detailed error messages like “Invalid signature” versus “User not found” can allow an attacker to distinguish between valid and invalid identifiers. Additionally, if JWT tokens have long expiration times or are not bound to a per-request nonce or IP context, an attacker who intercepts a token might replay it to probe account permissions or infer account behavior. The attack surface also includes unauthenticated endpoints used for login or token refresh, where weak account lockout or captcha mechanisms can enable rapid, automated attempts.
To detect this pattern, middleBrick runs authentication and rate limiting checks alongside account enumeration probes. In an OpenAPI/Swagger spec, the tool cross-references defined authentication flows (e.g., securitySchemes using type: http and scheme: bearer) with runtime behavior, validating that error responses remain consistent and that endpoints enforcing JWT verification do not leak user existence. The scanner does not rely on internal architecture; it observes inputs and outputs to identify whether brute force risks exist in the API design and implementation.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on rate limiting, consistent error handling, and secure token usage. Below are AdonisJS code examples that demonstrate these controls.
1. Apply rate limiting to authentication routes
Use AdonisJS middleware to limit login and token validation requests. This example uses the built-in Throttle middleware to cap attempts per IP.
// start/handlers/throttle-login.ts
import { Throttle } from '@ioc:Adonis/Addons/Throttle'
export const config = {
identifier: 'login-throttle',
maxAttempts: 5,
windowInSeconds: 60,
timeoutInSeconds: 300,
}
// start/routes.ts
import Route from '@ioc:Adonis/Core/Route'
import throttleLogin from 'App/Handlers/throttle-login'
Route.post('auth/login', 'AuthController.login').middleware(['throttle:login-throttle', 'validateLogin'])
Route.post('auth/token/refresh', 'AuthController.refresh').middleware(['throttle:login-throttle'])
2. Use consistent error responses to prevent account enumeration
Ensure that authentication responses do not reveal whether an account exists. Always perform token validation and database lookup before returning a standardized error payload.
// app/Controllers/Http/AuthController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema } from '@ioc:Adonis/Core/Validator'
import User from 'App/Models/User'
import { DateTime } from 'luxon'
export default class AuthController {
public async login({ request, auth, response }: HttpContextContract) {
const email = request.input('email')
const password = request.input('password')
// Validate input first to avoid unnecessary work
const loginSchema = schema.create({
email: schema.string.optional([
...email.isEmail ? [] : ['email'],
]),
password: schema.string.optional([
...password ? [] : ['required'],
]),
})
const payload = await request.validate({ schema: loginSchema })
// Always perform token validation if a token is provided
let user: User | null = null
if (payload.email) {
user = await User.findBy('email', payload.email)
}
// Standardized response regardless of user existence
if (!user || !(await user.verifyPassword(password))) {
return response.unauthorized({
error: {
code: 'auth_invalid_credentials',
message: 'Invalid credentials',
documentation: 'https://docs.example.com/auth/errors#auth_invalid_credentials',
},
})
}
const token = await auth.use('api').generate(user)
return response.ok({
token: token.toJSON(),
expiresIn: DateTime.local().plus({ hours: 1 }).toISO(),
})
}
}
3. Configure JWT options to limit token lifetime and scope
Keep token lifetimes short and avoid exposing sensitive claims. Configure the JWT provider to use appropriate options.
// config/jwt.ts
export const jwt = {
enabled: true,
secret: process.env.JWT_SECRET as string,
token: {
expiresIn: '15m',
notBefore: '0s',
issuer: 'api.example.com',
audience: 'web-app',
},
refreshToken: {
enabled: true,
expiresIn: '7d',
},
}
4. Validate token binding where applicable
Bind tokens to contextual properties such as IP or user-agent when your threat model requires it. This example stores the IP at issuance and validates it on sensitive operations.
// app/Models/User.ts
import { DateTime } from 'luxon'
import { column, beforeSave } from '@ioc:Adonis/Lucid/Orm'
import { Hash } from '@ioc:Adonis/Core/Hash'
import type { LucidModel } from '@ioc:Adonis/Lucid/Model'
import { v4 as uuidv4 } from 'uuid'
export default class User {
@column({ isPrimary: true })
public id: number
@column()
public email: string
@column()
public password: string
@column()
public ipAtTokenIssuance?: string
@beforeSave()
public static async hashPassword(user: User) {
if ($exposing(event, 'saving') && user.$dirty.password) {
user.password = await Hash.make(user.password)
}
}
}
// In a controller or middleware handling sensitive routes
public async verifyTokenContext({ auth, request, response }: HttpContextContract) {
const user = auth.user
const token = request.headers().authorization?.replace('Bearer ', '')
if (!token || !user) {
return response.unauthorized({ error: { code: 'auth_token_missing', message: 'Unauthorized' } })
}
// Example: check that the current request IP matches the issuance context
if (user.ipAtTokenIssuance && user.ipAtTokenIssuance !== request.ip()) {
return response.forbidden({ error: { code: 'token_context_mismatch', message: 'Token context invalid' } })
}
}
5. Secure OpenAPI/Swagger spec references for JWT
Ensure your spec defines bearer security schemes and that responses do not leak information. Use x-internal or x-unauthenticated tags for public routes and require security definitions for sensitive operations.
# openapi.yaml (excerpt)
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
securitySchemes:
oauth2:
type: oauth2
flows:
password:
tokenUrl: /auth/login
scopes: {}
security:
- bearerAuth: []