Replay Attack in Adonisjs
How Replay Attack Manifests in Adonisjs
A replay attack in an Adonisjs application occurs when an adversary intercepts a valid request—typically containing an authentication token, session identifier, or a signed nonce—and retransmits it later to impersonate the original user or escalate privileges. Adonisjs does not automatically protect against request duplication; it relies on the developer to enforce freshness and idempotency controls. Attackers commonly target login, password reset, and transaction endpoints where a predictable or missing anti-replay mechanism exists.
Specific Adonisjs code paths where replay vulnerabilities appear include:
- Stateless JWT-based APIs using
authwith custom tokens where the token lacks ajti(JWT ID) claim and short expiration, enabling captured tokens to be reused until they expire. - Password reset flows that accept a token via query or body without verifying a one-time-use constraint or timestamp window, allowing an intercepted token to be reused to change a password.
- Webhook endpoints or payment status callbacks that process GET or POST requests based solely on a shared secret or signature without a nonce or timestamp check, enabling an attacker to replay the same payload to trigger duplicate actions (e.g., creating a subscription or credit).
- GraphQL mutations in Adonisjs that rely only on cookie-based session authentication without request-level nonce or idempotency keys, permitting replay of state-changing operations like order placement or profile updates.
Example of a vulnerable token-based flow in Adonisjs where replay is feasible:
// routes.ts
Route.post('reset-password', 'AuthController.resetPassword')
// app/Controllers/Http/AuthController.ts
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class AuthController {
public async resetPassword({ request, auth }: HttpContextContract) {
const token = request.input('token')
const newPassword = request.input('password')
// Vulnerable: no check that token has been used before
const user = await User.verifyResetToken(token)
if (!user) return { error: 'Invalid token' }
await user.merge({ password: newPassword }).save()
return { message: 'Password updated' }
}
}
An attacker who observes a valid reset token can replay the same request to reset the password again or for another user if the token is not single-use and lacks temporal constraints.
Adonisjs-Specific Detection
Detecting replay attack risks in Adonisjs requires combining static analysis of routes, validators, and authentication logic with runtime behavior inspection. Key indicators include endpoints that accept tokens or signatures without a nonce, timestamp, or one-time-use verification, and responses that do not change after replaying the same request.
Using middleBrick to scan an Adonisjs API helps surface these issues by correlating OpenAPI/Swagger specs (with full $ref resolution) against runtime behavior. The scanner runs 12 security checks in parallel, including Authentication, Input Validation, Rate Limiting, and Unsafe Consumption, to identify missing anti-replay controls. For example, if an endpoint accepts a password reset token without enforcing a single-use policy or a short validity window, middleBrick flags it with severity and remediation guidance mapped to OWASP API Top 10 and common compliance frameworks.
To detect replay risks manually in Adonisjs codebases, review:
- Routes that expose token-based actions without requiring a fresh nonce or timestamp (e.g.,
Route.get('webhook/paypal', 'WebhookController.paypal')). - Validators and schemas that do not include fields like
timestampornonce, or that skip idempotency checks. - Authentication guards that issue long-lived tokens without
jtior without mechanisms to revoke or blacklist used tokens. - Controllers that perform side effects (e.g., creating records, charging payments) based solely on a signature or token without ensuring the request has not been processed before.
middleBrick’s LLM/AI Security checks are not applicable here because replay attacks are a transport/logic layer concern rather than an AI-specific risk; focus instead on the framework’s request handling patterns.
Adonisjs-Specific Remediation
Remediating replay attacks in Adonisjs centers on ensuring each request is unique and verifiable within a tight time window. Use Adonisjs native features such as the validator schema, the auth module, and database transactions to implement nonces, timestamps, and idempotency keys.
1. Add a timestamp and nonce to token-based flows:
// app/Validators/ResetPassword.ts
import { schema } from '@ioc:Adonis/Core/Validator'
export default class ResetPasswordValidator {
public schema = schema.create({
token: schema.string({}, [rules.exists({ table: 'password_reset_tokens', column: 'token' })]),
timestamp: schema.date(),
nonce: schema.string({}, [rules.unique({ table: 'api_nonces', column: 'value' })]),
})
public messages = {
'timestamp.date': 'The timestamp must be a valid date.',
'nonce.unique': 'This request has already been processed.',
}
}
// app/Controllers/Http/AuthController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { DateTime } from 'luxon'
export default class AuthController {
public async resetPassword({ request, auth }: HttpContextContract) {
const { token, password, timestamp, nonce } = request.only(['token', 'password', 'timestamp', 'nonce'])
const now = DateTime.local()
const reqTime = DateTime.fromISO(timestamp)
// Reject if older than 5 minutes
if (now.diff(reqTime, 'minutes').minutes! > 5) {
return { error: 'Request expired' }
}
const user = await User.verifyResetToken(token)
if (!user) return { error: 'Invalid token' }
await user.merge({ password }).save()
// Store nonce to prevent reuse
await ApiNonce.create({ value: nonce, expiresAt: reqTime.plus({ minutes: 10 }).toJSDate() })
return { message: 'Password updated' }
}
}
2. Use idempotency keys for state-changing operations:
// app/Controllers/Http/OrderController.ts
import { HttpContextContract } from '@ioc:Adonis/core/HttpContext'
import { DateTime } from 'luxon'
export default class OrderController {
public async create({ request, auth, response }: HttpContextContract) {
const idempotencyKey = request.header('Idempotency-Key')
if (!idempotencyKey) return response.badRequest({ error: 'Idempotency-Key header required' })
// Ensure key is unique per user and recent
const existing = await ApiIdempotency.findBy('key', idempotencyKey)
if (existing) return existing.response
// Process order and store idempotency record atomically
const order = await Order.create({ ...request.body, userId: auth.user!.id })
await ApiIdempotency.create({ key: idempotencyKey, response: order.serialize() })
return order
}
}
3. Enforce short token lifetimes and rotate secrets regularly in config/auth.ts, and prefer signed cookies or encrypted JWTs with jti claims where supported.