HIGH race conditionfeathersjsjwt tokens

Race Condition in Feathersjs with Jwt Tokens

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

A race condition in a FeathersJS application that uses JWT tokens typically occurs when token validity checks and state-changing operations are not performed atomically. Because FeathersJS services are event-driven and often rely on hooks for authentication, a time-of-check-to-time-of-use (TOCTOU) window can exist between verifying a JWT and acting on the request. Within this window, an attacker can manipulate shared state in a way that invalidates the assumptions made during the check.

Consider an endpoint that transfers a resource ownership. The JWT payload may include a user identifier, and a hook verifies the token and attaches the user to the request. If the service then performs a find query to confirm the resource exists and belongs to the user, followed by an update, another concurrent request can modify the resource between the find and the update. This can lead to horizontal privilege escalation, where one user acts on another’s resource, or to inversion-of-control where an attacker reuses an old token after a state change (such as revocation or role update) has already been applied but not yet observed by the first request.

JWTs themselves are stateless; their validity depends on signature verification and claims checks (issuer, audience, expiration, not-before). If an application does not maintain server-side state for tokens (e.g., a denylist or short-lived token binding), a compromised token remains usable until expiration. When combined with FeathersJS hooks that only validate the token once per request, an attacker may exploit timing differences between token validation and the underlying data operations. For example, if revocation is implemented by adding a token identifier to a denylist and the check occurs in a hook, a second request that passed the hook before revocation can still execute business logic if it is already in flight. This is a classic race condition: the token’s logical invalidation does not synchronize with ongoing requests that have already passed the authentication check.

Real attack patterns include concurrent modification of records, token replay within the validity window, and exploitation of delayed revocation checks. The OWASP API Security Top 10 category ‘Broken Object Level Authorization’ (BOLA) intersects with this issue when authorization decisions do not account for concurrent state changes. Similarly, improper handling of JWT claims such as ‘jti’ (JWT ID) or missing one-time-use semantics can enable replay or substitution attacks. Because FeathersJS typically uses JSON payloads over HTTP, there is no built-in replay protection unless explicitly implemented.

To detect such issues, scanning with a tool that understands both API behavior and token semantics is valuable. middleBrick scans unauthenticated attack surfaces and can surface authorization inconsistencies and input validation gaps across the 12 security checks, including BOLA/IDOR and Authentication. Its OpenAPI/Swagger analysis resolves $ref chains and cross-references spec definitions with runtime findings, which helps identify mismatches between declared security schemes and actual runtime behavior. While middleBrick does not fix the code, it provides prioritized findings with severity and remediation guidance, enabling developers to address the race condition in FeathersJS with JWT tokens.

Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on making token validation and state changes atomic and consistent, and ensuring JWT claims are strictly validated. Below are concrete code examples for a FeathersJS service hook that addresses race conditions when using JWT tokens.

First, always validate JWT claims rigorously and bind tokens to a minimal scope. Use the jsonwebtoken library to verify and decode, and enforce ‘iss’, ‘aud’, ‘exp’, and ‘nbf’. Enforce a short expiration window and consider one-time-use identifiers where appropriate.

const jwt = require('jsonwebtoken');

function verifyToken(token) {
  const publicKey = process.env.JWT_PUBLIC_KEY;
  const decoded = jwt.verify(token, publicKey, {
    algorithms: ['RS256'],
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com',
    clockTolerance: 60
  });
  // Ensure a JWT ID is present and optionally check a denylist here
  if (!decoded.jti) {
    throw new Error('Invalid token: missing jti');
  }
  return decoded;
}

Second, implement hook logic that fetches the current resource state immediately before the write operation, and re-check authorization within the same transactional context where supported. For databases that support transactions, wrap the read and write in a transaction to reduce the window for race conditions.

// In a FeathersJS service hook
async function ensureOwnership(context) {
  const { user } = context;
  const { id } = context.params.query;
  const resource = await context.app.service('resources').get(id);
  if (!resource) {
    throw new Error('Not found');
  }
  if (resource.userId !== user.user_id) {
    throw new Error('Unauthorized');
  }
  // Re-validate token claims that are relevant for this operation
  if (context.params.token) {
    verifyToken(context.params.token);
  }
  // Attach enriched data for downstream handlers
  context.params.currentResource = resource;
  return context;
}

Third, to mitigate replay and state-synchronization issues, prefer short-lived JWTs and validate a denylist (or a last-seen timestamp) on every request. Store denylist entries with an expiry matching token TTL, and check them synchronously during authentication hooks.

const denylist = new Set(); // In production, use a shared fast store with TTL

function isTokenRevoked(jti) {
  return denylist.has(jti);
}

// In hook
async function checkRevocation(context) {
  const token = context.params.token;
  if (!token) return context;
  const decoded = verifyToken(token);
  if (isTokenRevoked(decoded.jti)) {
    throw new Error('Token revoked');
  }
  return context;
}

Finally, for deployments that use the middleBrick CLI to validate configurations and the GitHub Action to gate CI/CD, you can integrate these checks into your pipeline. The CLI can scan your service definitions for missing ownership checks and the GitHub Action can fail builds if risk scores exceed your threshold, prompting a review of authorization logic before merge.

Frequently Asked Questions

How does middleBrick detect race conditions involving JWT tokens in FeathersJS?
middleBrick runs 12 parallel security checks, including Authentication and BOLA/IDOR, against the unauthenticated attack surface. By analyzing OpenAPI/Swagger specs with full $ref resolution and correlating runtime findings, it highlights inconsistencies in authorization flows and missing atomic checks that can enable race conditions.
Can middleBrick fix race conditions in FeathersJS with JWT tokens?
middleBrick detects and reports findings with severity and remediation guidance; it does not fix, patch, block, or remediate. Developers should apply atomic token validation and state checks in hooks, use short-lived JWTs, and enforce denylist checks as shown in the code examples.