Time Of Check Time Of Use in Feathersjs with Api Keys
Time Of Check Time Of Use in Feathersjs with Api Keys — how this specific combination creates or exposes the vulnerability
Time Of Check Time Of Use (TOCTOU) occurs when the outcome of a security decision depends on the timing between a check and the subsequent use of a resource. In FeathersJS applications that rely on API keys for authentication, TOCTOU can manifest when authorization checks are performed separately from the actual service operation, creating a window where state changes can invalidate earlier decisions.
Consider a FeathersJS service that first validates an API key via an authentication hook, then later performs a lookup to verify whether the associated client is permitted to access a specific resource. If the check and the use are not atomic, an attacker can change the underlying data between these steps. For example, an API key might initially be valid for organization A, but before the service uses the key to fetch organization-specific data, the key is reassigned to organization B. Because the check and use are separate, the service may operate on organization A’s data using credentials that now belong to organization B, effectively bypassing intended isolation.
In FeathersJS, hooks run as middleware and can introduce TOCTOU when they perform non-atomic validation patterns. A common pattern is to look up a key in one hook to attach metadata (e.g., organization ID) to the params object, then let a service handler assume that metadata is still accurate when it executes. If the key’s permissions or associations are mutable and can be changed by an authenticated actor or through another API endpoint, the handler may execute with stale assumptions. This is particularly risky when API keys are used for impersonation or tenant segregation, as the handler may unknowingly operate with elevated or incorrect context.
Real-world attack patterns mirror issues described in OWASP API Top 10 (2023), such as Broken Function Level Authorization (BFLA), where lack of proper context validation enables privilege escalation. For example, an attacker could call a mutation that updates key bindings in one request, then immediately invoke a data-fetching endpoint that relied on a prior key validation check. If the service does not re-validate context at the point of use, the attacker may access or modify data they should not reach.
Additionally, if the service exposes an unauthenticated endpoint that reports key metadata or status, an attacker can probe timing differences to infer when checks and uses are separated. Combined with the inherent concurrency model of Node.js runtimes in FeathersJS, this can amplify the impact of TOCTOU by making race conditions more likely. The framework does not provide built-in atomicity guarantees for hook-to-handler transitions, so developers must design checks and usage to be tightly coupled or re-validate critical decisions immediately before performing sensitive operations.
Using OpenAPI/Swagger analysis with full \$ref resolution can help identify whether authorization checks are duplicated across paths and whether definitions inadvertently encourage split validation logic. When scanning a FeathersJS API, pay attention to operations that accept API keys in headers but then rely on cached or previously attached claims without re-verifying them against the current request context.
Api Keys-Specific Remediation in Feathersjs — concrete code fixes
To mitigate TOCTOU in FeathersJS when using API keys, ensure that authorization decisions are made as close as possible to the point of use, and avoid relying on mutable state that can change between a check and a use. Prefer re-validating the key and its permissions within the same hook or service method, and avoid attaching long-lived assumptions to params that can be invalidated by other endpoints.
Below are concrete code examples demonstrating secure patterns.
Example 1: Atomic validation inside a service method
Instead of attaching key metadata in a prior hook and trusting it later, validate the key and fetch permissions within the service method itself. This removes the timing gap.
// services/items/service.js
const { Forbidden } = require('@feathersjs/errors');
module.exports = function (service) {
return async context => {
const { params } = context;
const apiKey = params.headers['x-api-key'];
if (!apiKey) {
throw new Forbidden('API key is required');
}
// Re-fetch key and permissions for every request
const keyRecord = await params.app.service('api-keys').get(apiKey, params);
if (!keyRecord || keyRecord.revoked) {
throw new Forbidden('Invalid or revoked API key');
}
// Re-validate tenant/resource association immediately before use
const item = await service.get(context.id, context.params);
if (item.organizationId !== keyRecord.organizationId) {
throw new Forbidden('API key does not permit access to this resource');
}
return item;
};
};
Example 2: Using a custom hook that re-validates within a transaction-like flow
Create a hook that performs validation and, if possible, keeps the operation within a single logical flow. While FeathersJS does not provide database transactions across all adapters, you can minimize state changes by avoiding intermediate writes that can alter key associations.
// hooks/validate-api-key.js
const { Forbidden } = require('@feathersjs/errors');
module.exports = function () {
return async context => {
const apiKey = context.params.headers['x-api-key'];
if (!apiKey) {
throw new Forbidden('API key missing');
}
// Fetch key with necessary relations in one call
const keyInfo = await context.app.service('api-keys').get(apiKey, context.params, {
query: { $select: ['id', 'organizationId', 'scopes', 'revoked'] }
});
if (keyInfo.revoked) {
throw new Forbidden('API key revoked');
}
// Attach minimal, verified metadata for downstream use
context.params.verifiedOrgId = keyInfo.organizationId;
context.params.scopes = keyInfo.scopes;
return context;
};
};
Example 3: Securing mutable key bindings with immediate re-check
If your API allows updating key bindings, ensure that any sensitive operation re-checks the binding immediately before acting, rather than trusting earlier attachments.
// services/data/service.js
module.exports = function (service) {
return async context => {
const requestedId = context.id;
const apiKey = context.params.headers['x-api-key'];
const keyRecord = await context.app.service('api-keys').get(apiKey, context.params);
// Re-validate right before data access
const record = await service.get(requestedId, context.params);
if (record.tenantId !== keyRecord.tenantId) {
throw new (require('@feathersjs/errors').Forbidden)('Tenant mismatch');
}
return record;
};
};
These examples emphasize re-validation at the point of use, avoiding reliance on stale metadata, and keeping authorization checks tightly coupled with the operation. They align with best practices for mitigating TOCTOU and reduce the risk of BOLA/IDOR and BFLA-like issues in FeathersJS APIs using API keys.