HIGH race conditionadonisjsjwt tokens

Race Condition in Adonisjs with Jwt Tokens

Race Condition in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

A race condition in AdonisJS when using JWT tokens typically occurs around token invalidation or revocation checks during concurrent requests. Because JWTs are often validated statelessly (signature and claims), developers sometimes add a small cache or database check (e.g., token denylist or user status) between verification and authorization. If two requests arrive at nearly the same time before the cache/database is updated, both may pass validation, enabling a privilege escalation or unauthorized action (BOLA/IDOR-like outcome).

Consider an endpoint that changes a user’s email. The flow is: verify JWT, check if the user intends to revoke old sessions (e.g., via a denylist table), then update the email. An attacker who triggers a revoke (e.g., logs out from all devices) and immediately sends a crafted request can race the denylist write. Because AdonisJS may handle these requests on different event loop ticks or worker threads, both requests might read an empty denylist, causing the second request to apply despite the logout intent. This is not a flaw in JWT itself but in how stateful checks are sequenced around an otherwise stateless token.

In AdonisJS, this often maps to the Authorization and BOLA/IDOR checks among the 12 parallel security scans. The scanner tests whether authorization logic is subject to timing gaps by simulating concurrent calls with different token states. If the application relies on per-request database or cache checks without atomic safeguards, the scan may surface a high-severity finding tied to authorization integrity.

Real-world patterns that can trigger this include: using a denylist table for token blacklisting without row-level locking, checking user status in a pre-handler hook that runs after JWT verification, or performing sensitive mutations after asynchronous guards that do not lock the resource. These patterns are especially risky when tokens carry broad scopes or when endpoints mutate state based on claims that can be stale between checks.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

To mitigate race conditions with JWT tokens in AdonisJS, favor stateless verification where possible and ensure that any stateful checks are atomic and ordered. Below are concrete patterns and code examples.

1. Prefer stateless verification and short-lived tokens

Keep token validation purely cryptographic. Avoid post-verification database lookups unless absolutely necessary. Use short expirations and rotate secrets regularly.

import { verify } from 'jsonwebtoken'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class AuthMiddleware {
  public async handle(ctx: HttpContextContract) {
    const token = ctx.request.header('authorization')?.replace('Bearer ', '')
    if (!token) {
      return ctx.response.unauthorized('Missing token')
    }
    try {
      const payload = verify(token, process.env.JWT_SECRET!, { algorithms: ['HS256'] })
      ctx.auth.user = payload as any
    } catch (error) {
      return ctx.response.unauthorized('Invalid token')
    }
  }
}

2. Atomic denylist checks with database transactions or locking

If you must maintain a denylist (e.g., for logout), perform the check within a transaction or use a lock to serialize concurrent reads/writes. In AdonisJS with Lucid, you can use DB.transaction and SELECT … FOR UPDATE where supported.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Database from '@ioc:Adonis/Lucid/Database'
import { DateTime } from 'luxon'

export default class TokenDenylistCheck {
  public static async handle(ctx: HttpContextContract, next: () => Promise) {
    const token = ctx.request.header('authorization')?.replace('Bearer ', '')
    if (!token) {
      return ctx.response.unauthorized('Missing token')
    }
    // Assume payload contains jti and exp
    const { jti, exp } = verify(token, process.env.JWT_SECRET!) as any
    const now = Math.floor(Date.now() / 1000)
    if (exp < now) {
      return ctx.response.unauthorized('Token expired')
    }
    // Atomic check using transaction with lock
    await Database.transaction(async (trx) => {
      const entry = await trx
        .from('token_denylist')
        .where('jti', jti)
        .lockForUpdate()
        .first()
      if (entry) {
        throw new Error('Token revoked')
      }
    })
    await next()
  }
}

3. Idempotent mutations and versioning

Make state-changing endpoints idempotent using request IDs or versioned resources. This reduces the impact of a successful race by ensuring repeated requests do not change outcome unexpectedly.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import RequestId from 'uuid-by-string'

export default class UpdateEmail {
  public static async handle(ctx: HttpContextContract) {
    const { newEmail } = ctx.request.body()
    const requestId = ctx.request.id() // or a header like X-Request-Id
    const user = ctx.auth.user
    // Use requestId to deduplicate or order operations safely
    await user.related('emails').query().where({ requestId }).delete()
    await user.related('emails').create({ email: newEmail, requestId })
    ctx.response.ok({ updated: true })
  }
}

4. Server-side session invalidation instead of token revocation

For sensitive operations (e.g., email change), require re-authentication or use server-side sessions to invalidate prior sessions, avoiding reliance on token denylists subject to races.

// Example: Require recent re-auth for email change
export default class RequireReauth {
  public static async handle(ctx: HttpContextContract, next: () => Promise) {
    const reauthAt = ctx.session.get('reauthAt')
    const now = Date.now()
    if (!reauthAt || now - reauthAt > 15 * 60 * 1000) {
      return ctx.response.unauthorized('Reauthentication required')
    }
    await next()
  }
}

Frequently Asked Questions

Does middleBrick test for race conditions involving JWT tokens in AdonisJS?
Yes. The scanner includes checks for authorization timing gaps around JWT validation and flags cases where concurrent requests may bypass intended access controls.
Can I see a detailed report with remediation examples in the dashboard?
Yes. The Web Dashboard provides prioritized findings with severity, remediation guidance, and links to relevant code patterns; the CLI can output JSON for integration into scripts.