HIGH time of check time of usefeathersjsjwt tokens

Time Of Check Time Of Use in Feathersjs with Jwt Tokens

Time Of Check Time Of Use in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Time Of Check Time Of Use (TOCTOU) is a class of race condition where the outcome of an operation depends on the timing between a check and the subsequent use of a resource. In FeathersJS applications that use JWT tokens for authentication, TOCTOU can occur when authorization logic depends on token metadata that may become stale between validation and action execution.

Consider a typical FeathersJS service flow where a JWT access token is verified and the decoded payload is used to locate and modify a resource. The framework verifies the token signature and extracts the user identifier, often mapping it to a database record such as a user profile. A vulnerability arises if the check (e.g., confirming the user owns the resource) and the use (e.g., updating or deleting the resource) are not performed within a consistent authorization context. An attacker who can alter their own resource between the authorization check and the data access step might exploit timing differences, especially in distributed deployments where token validation and service calls are not tightly synchronized.

With JWT tokens, the risk is amplified when the token payload includes roles or permissions that are not re-validated at the point of data access. FeathersJS applications commonly rely on hooks that attach the user object from the token to params. If a hook performs a permission check and later the service implementation performs a separate lookup without reconfirming context, an attacker could manipulate their own data in a way that passes the initial check but violates intended constraints at use time. For example, a user could change their role in a way that is not reflected in the cached token payload, yet the server proceeds based on the older, cached role data embedded in the token.

Additionally, token revocation scenarios highlight the TOCTOU exposure. JWTs are often designed to be stateless, meaning revocation is not enforced until expiration. If FeathersJS performs an authorization check based on token claims and then performs an action that should be blocked by a recent revocation (e.g., logout or privilege removal), the window between revocation and token expiry creates a race condition. This is particularly relevant when using short-lived access tokens combined with refresh mechanisms, where a stolen token might still pass checks until it naturally expires, but the backend state has already changed.

In distributed deployments, network latency and load can exacerbate TOCTOU issues. A token validation performed on one instance might not immediately reflect changes made on another instance, such as updated user permissions or revoked sessions. Because FeathersJS services often scale horizontally, relying on in-memory or local caches for authorization checks without atomic context binding can lead to inconsistent decisions between the check and the use phases.

To mitigate these risks, authorization logic in FeathersJS should bind the check and use together within a single, consistent context. This means performing data ownership or permission verification at the point of access rather than relying solely on pre-verified token claims. Developers must ensure that any decision derived from a JWT token is reconfirmed using the current state from the data store at the moment of the operation, effectively eliminating the timing gap that enables TOCTOU.

Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on ensuring that authorization checks occur immediately before the data operation and always use the latest state from the backend data store, rather than relying exclusively on token claims that may be stale.

One effective pattern is to move ownership and permission checks into service hooks so that they run within the same request lifecycle as the operation. This ensures that the data retrieved for the operation is verified against the current database state. Below is an example of a FeathersJS hook that re-validates user ownership using the user identifier from the token and the record being accessed.

// src/hooks/ensure-ownership.js
module.exports = function ensureOwnership() {
  return async context => {
    const { user } = context.params;
    const { id } = context.result || context.data;

    if (!user || !id) {
      throw new Error('Unauthorized');
    }

    // Assume context.app.service('users') is available and can fetch current user state
    const userService = context.app.service('users');
    const currentUser = await userService.get(user.id);

    // Re-validate that the resource belongs to the authenticated user
    if (context.result.userId !== currentUser._id.toString()) {
      throw new Error('Forbidden: Ownership mismatch');
    }

    return context;
  };
};

In this hook, context.result or context.data represents the resource about to be created or modified. The hook fetches the latest user record from the users service and compares the resource’s user identifier with the authenticated user’s current identifier. This check runs immediately before the operation proceeds, closing the TOCTOU window.

For update and patch operations, a similar approach should validate that the record being updated still belongs to the requesting user. Below is an example tailored to a todos service.

// src/hooks/todo-ownership.js
module.exports = function todoOwnership() {
  return async context => {
    const { user } = context.params;
    const { id } = context.id !== undefined ? { id: context.id } : context.data;

    if (!user || !id) {
      throw new Error('Unauthorized');
    }

    const todo = await context.app.service('todos').get(id);
    if (!todo || todo.userId.toString() !== user.id.toString()) {
      throw new Error('Forbidden: You do not own this todo');
    }

    return context;
  };
};

Developers should also configure the JWT secret and audience options consistently across services to avoid token validation inconsistencies. Below is a minimal FeathersJS authentication configuration that ensures tokens are validated with the same parameters across instances.

// src/authentication.js
const authentication = require('@feathersjs/authentication');
const jwt = require('@feathersjs/authentication-jwt');

module.exports = function configureAuthentication(app) {
  const config = app.get('authentication');

  app.configure(authentication(config));
  app.configure(jwt({
    secret: config.secret,
    audience: config.audience,
    issuer: config.issuer
  }));
};

Finally, avoid storing mutable authorization data solely in the JWT payload. If roles or permissions must be reflected in token claims, implement a short token lifetime and use a side-channel data store to verify current authorization status at the point of use. This approach limits the impact of any stale token claims and reduces the feasibility of TOCTOU attacks in FeathersJS applications using JWT tokens.

Frequently Asked Questions

Why does moving the ownership check into a hook reduce TOCTOU risk with JWT tokens?
Because the hook fetches the latest record from the data store immediately before the operation, ensuring the authorization decision uses current state rather than potentially stale token claims.
Can short-lived JWT tokens alone prevent TOCTOU vulnerabilities in FeathersJS?
No. Short lifetimes reduce the window of exposure but do not eliminate TOCTOU; authorization must still be re-validated against the current data store state at the moment of access.