HIGH replay attackadonisjsbasic auth

Replay Attack in Adonisjs with Basic Auth

Replay Attack in Adonisjs with Basic Auth — how this specific combination creates or exposes the vulnerability

A replay attack occurs when an attacker intercepts a valid request and retransmits it to reproduce the original effect. Using HTTP Basic Auth in AdonisJS can expose this risk when transport protections are absent or when tokens/credentials are reused across multiple requests without additional protections.

With Basic Auth, credentials (username and password) are encoded in Base64 and sent in the Authorization header. If an attacker captures this header—such as on an unencrypted channel or via a compromised proxy—they can replay the same header to impersonate the user. AdonisJS does not inherently prevent replay by adding per-request nonces or timestamps for Basic Auth requests, so the same encoded credentials remain valid across repeated submissions.

Endpoints that rely solely on Basic Auth for authentication without additional context—such as a monotonically increasing request ID, a timestamp, or a cryptographic nonce—are vulnerable. For example, an API endpoint that accepts fund transfers or configuration changes via POST with only Basic Auth can be exploited if an attacker replays a captured request with the same credentials and body.

Consider an AdonisJS route that uses only Basic Auth middleware:

import Route from '@ioc:Adonis/Core/Route'

Route.post('/transfer', async ({ auth, request }) => {
  const user = auth.getUserOrFail()
  const { to, amount } = request.only(['to', 'amount'])
  // process transfer
  return { ok: true }
}).middleware(['auth:basic'])

If an attacker captures a request like:

POST /transfer HTTP/1.1
Authorization: Basic dXNlcjpwYXNz
Content-Type: application/json

{ "to": "attacker_id", "amount": 100 }

they can replay it to execute the same transfer. Basic Auth does not bind the request to a particular session or include a server-generated challenge, so the replay is accepted if the credentials are valid and no additional checks are in place.

The risk is compounded when TLS is not enforced or is improperly configured, as Base64 encoding offers no confidentiality. Even with TLS, replay can occur within the valid session window if an attacker gains access to the headers or if logs inadvertently expose the Authorization header. AdonisJS applications must implement additional mechanisms—such as per-request nonces, timestamp validation, or moving away from Basic Auth in favor of token-based schemes with built-in replay protection—to mitigate this class of attack.

Basic Auth-Specific Remediation in Adonisjs — concrete code fixes

To mitigate replay attacks while continuing to use HTTP Basic Auth in AdonisJS, you should introduce request uniqueness properties and validate them server-side. Common approaches include requiring an anti-replay timestamp or nonce in headers and enforcing short validity windows.

One practical pattern is to require a custom header such as X-Request-Timestamp and verify that it falls within an acceptable time skew (for example, ±2 minutes). This prevents replayed requests from outside the window from being accepted.

import Route from '@ioc:Adonis/Core/Route'
import { DateTime } from 'luxon'

Route.post('/secure-action', async ({ auth, request, response }) => {
  const user = auth.getUserOrFail()
  const timestampHeader = request.header('x-request-timestamp')
  if (!timestampHeader) {
    return response.badRequest({ error: 'Missing timestamp' })
  }
  const requestTime = DateTime.fromISO(timestampHeader)
  const now = DateTime.local()
  const diff = now.diff(requestTime, 'minutes').minutes
  if (Math.abs(diff) > 2) {
    return response.unauthorized({ error: 'Request expired' })
  }
  const { action } = request.only(['action'])
  // proceed with action
  return { ok: true }
}).middleware(['auth:basic'])

You can also use a nonce stored in a short-lived cache (e.g., an in-memory map or Redis) to ensure that the same nonce value is not reused with the same credentials. Include a server-generated nonce in a prior handshake step if feasible, or require clients to generate UUIDs for each request and track recently seen values.

import Route from '@ioc:Adonis/Core/Route'
import { v4 as uuidv4 } from 'uuid'

// Simple in-memory seen nonces (replace with Redis in production)
const seenNonces = new Set()

Route.post('/action', async ({ auth, request, response }) => {
  const user = auth.getUserOrFail()
  const nonce = request.header('x-request-nonce')
  if (!nonce) {
    return response.badRequest({ error: 'Missing nonce' })
  }
  if (seenNonces.has(nonce)) {
    return response.forbidden({ error: 'Replay detected' })
  }
  seenNonces.add(nonce)
  // prune old nonces periodically in real implementation
  const { operation } = request.only(['operation'])
  return { result: `Executed ${operation}` }
}).middleware(['auth:basic'])

While these measures reduce replay risk, consider replacing Basic Auth with token-based authentication (e.g., JWT with short expiry and unique jti claims, or OAuth2) where possible, as they offer more standardized replay protections. middleBrick’s scans can help identify endpoints that rely only on Basic Auth and surface related security findings when you scan your API.

Frequently Asked Questions

Can middleware be reused across multiple routes in AdonisJS to enforce timestamp and nonce checks for Basic Auth?
Yes. Define a custom middleware in AdonisJS (e.g., start/handle/replayProtection.ts) that checks x-request-timestamp and x-request-nonce, then apply it alongside the auth:basic middleware on routes. Register it in server.ts and reuse it across routes to ensure consistent replay protections.
How can I verify that my replay protections are effective without access to a pentest vendor?
Use the middleBrick CLI to scan your API: middlebrick scan . Inspect findings related to Authentication and replay risks. Combine this with manual tests that replay requests with identical headers and timestamps/nonces to confirm rejection.