HIGH use after freeadonisjsjwt tokens

Use After Free in Adonisjs with Jwt Tokens

Use After Free in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Use After Free occurs when memory that has been deallocated is later accessed. In the context of AdonisJS applications that use JWT tokens, this typically manifests not as a classic memory safety issue (since Node.js manages memory), but as a logical "use after free" where an invalidated or expired token is treated as valid, or where a recycled identifier (such as a user ID or session key) is reused while old token state remains trusted. This can happen when token validation logic does not adequately verify token freshness, revocation status, or binding to a current authorization context, allowing an attacker to reuse a token after it should have been invalidated (e.g., after logout, after password change, or after privilege changes).

With JWT tokens in AdonisJS, a common pattern is to decode and verify a token on each request, then attach the payload to the request object for downstream use. If the application does not check revocation lists, token versioning, or per-request freshness indicators (such as jti, iat, or a server-side denylist), an attacker who obtained a token earlier may continue to use it after the user’s permissions have been reduced or after the token should have been considered stale. For example, an attacker who captured a token with elevated scopes before a role change may find that the token still passes validation because the application trusts the embedded scopes without rechecking the database. This is a logical Use After Free: the token’s effective authorization context has been freed (changed on the server), but the token is still accepted as if it were valid in its previous state.

Additionally, if AdonisJS routes or controllers reuse objects, caches, or request-scoped variables without properly clearing or reinitializing them, a token or its claims may be inadvertently associated with a different user or execution context. This can occur when middleware caches decoded token data across requests or when route handlers assume that decoded token fields are still authoritative after related data has been rotated (e.g., signing keys rotated without invalidating existing tokens). The risk is compounded when the token payload contains identifiers that are reused (such as numeric user IDs) without validating that the token’s other attributes (iss, sub, scopes, session version) remain consistent with the current backend state.

In API security testing with middleBrick, such issues appear as broken access control or authorization findings, often mapped to the OWASP API Top 10 category A01:2019. middleBrick runs checks that compare token payload claims against runtime behavior and can detect scenarios where tokens remain accepted after privilege changes or logout events. Its OpenAPI/Swagger analysis correlates endpoint definitions with authentication requirements and can highlight missing revocation checks or inconsistent token binding across $ref-resolved specs.

Real-world attack patterns involving JWTs include token replay, privilege escalation via scope manipulation, and lateral movement across accounts with reused identifiers. Because JWTs are self-contained, developers must ensure that validation includes up-to-date server-side state, not just cryptographic correctness. Tools like middleBrick include checks for weak token handling, missing nonce or jti validation, and improper scope enforcement, helping teams identify logical Use After Free conditions before they are exploited.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on ensuring that every request validates not only the token’s cryptographic integrity but also its freshness, revocation status, and binding to the current authorization context. Below are concrete, realistic code examples for AdonisJS that demonstrate how to implement these protections.

First, maintain a server-side token denylist (e.g., in Redis) for revoked tokens and check it during validation. Store a stable token identifier (jti) and an incremental token version or a last-password-change timestamp in both the token and your user record, and verify them on each request.

// config/token.js
module.exports = {
  secret: process.env.JWT_SECRET,
  options: {
    expiresIn: '15m',
    issuer: 'myapp',
    algorithms: ['HS256'],
  },
};

// start/hooks/validate_jwt.js
const { HttpException } = require('@adonisjs/framework/src/Exceptions');
const { verify } = require('jsonwebtoken');
const TokenDenylist = use('App/Models/TokenDenylist'); // or a Redis client

async function validateJwt({ request, auth }, next) {
  const authorization = request.header('authorization');
  if (!authorization || !authorization.startsWith('Bearer ')) {
    throw new HttpException(401, 'Unauthorized: missing bearer token');
  }
  const token = authorization.split(' ')[1];

  // Check denylist first (revoked or logged out)
  const isRevoked = await TokenDenylist.isRevoked(token); // implement caching
  if (isRevoked) {
    throw new HttpException(401, 'Token revoked');
  }

  try {
    const decoded = verify(token, process.env.JWT_SECRET);
    // Enforce token versioning: ensure user's current tokenVersion matches the one in the token
    const user = await User.find(decoded.sub);
    if (!user || user.tokenVersion !== decoded.token_version) {
      throw new HttpException(401, 'Token no longer valid');
    }
    request.authTokenPayload = decoded;
  } catch (error) {
    throw new HttpException(401, 'Invalid token');
  }

  await next();
}

module.exports = { validateJwt };

Second, issue tokens with a jti (JWT ID) and a token_version claim derived from user state (such as last password change or role update). When rotating signing keys or changing user permissions, increment token_version so existing tokens fail validation.

// app/Controllers/Http/AuthController.js
const { sign } = require('jsonwebtoken');

class AuthController {
  async login({ request, auth }) {
    const { email, password } = request.all();
    const user = await User.findBy('email', email);
    // validate password omitted for brevity
    const token = sign(
      {
        sub: user.id,
        email: user.email,
        roles: user.roles,
        token_version: user.token_version, // increment on password/role change
        jti: `${user.id}-${Date.now()}`,   // unique per token
        iat: Math.floor(Date.now() / 1000),
      },
      Env.get('JWT_SECRET'),
      { expiresIn: '15m', issuer: 'myapp' }
    );
    return { token };
  }
}

Third, ensure that authorization checks revalidate scope and role on each request rather than trusting payload values indefinitely. For sensitive endpoints, perform a lightweight database or policy check to confirm that the user’s current permissions still match the token’s claims.

// app/Policies/ScopePolicy.js
class ScopePolicy {
  async verifyScope({ auth }, requiredScope) {
    const user = await User.find(auth.user.id);
    // re-fetch current roles/scopes to avoid stale token claims
    if (!user || !user.hasScope(requiredScope)) {
      throw new Error('Insufficient scope');
    }
    return true;
  }
}

// In a controller
class UserController {
  async updateProfile({ request, auth }) {
    await ScopePolicy.verifyScope(auth, 'profile:write');
    // proceed with update using current DB state
  }
}

Finally, rotate signing keys periodically and implement a short token lifetime to reduce the window for replay. Combine these measures with middleware that rejects tokens with outdated iat or unexpected iss, and enforce that critical operations require fresh authentication. middleBrick scans can validate that your API endpoints consistently enforce these checks and flag missing token binding or revocation logic as high-risk findings.

Frequently Asked Questions

How does middleBrick detect logical Use After Free with JWT tokens in AdonisJS APIs?
middleBrick compares JWT payload claims (such as scopes, roles, and token_version) against runtime authorization behavior and OpenAPI definitions. It flags cases where tokens remain accepted after privilege changes or logout and highlights missing revocation or token binding checks.
Can middleware-based token validation fully prevent Use After Free in AdonisJS?
Middleware validation significantly reduces risk, but prevention also requires server-side denylists, token versioning tied to user state (e.g., password change counters), and revalidation of scope and permissions on sensitive operations. Continuous monitoring and scans help detect regressions.