Injection Flaws in Feathersjs with Jwt Tokens
Injection Flaws in Feathersjs with Jwt Tokens
FeathersJS is a framework for real-time applications that often uses JWT tokens for stateless authentication. When JWT handling and service-side query building intersect, injection flaws can emerge. An attacker may supply a malformed or malicious token, or exploit service parameters that are not properly constrained, leading to unintended data access or execution paths. Because FeathersJS services commonly accept query objects that are merged into database adapters, unchecked inputs derived from token claims or from parameters influenced by token data can become injection vectors.
Consider a service that trusts a user ID claim in a JWT to filter records without additional authorization checks. If the service exposes a REST or real-time endpoint where query parameters are not validated, an attacker who obtains or guesses a valid token can manipulate the query to retrieve or affect data belonging to other users. This can amplify issues such as BOLA/IDOR when combined with weak ownership checks. Moreover, if the application dynamically builds queries using token payload fields (e.g., tenant identifiers) without strict allowlists, attackers may inject operators or unexpected keys that change the semantics of the query.
In some configurations, injection flaws can also arise from how FeathersJS hooks and middleware process data before it reaches the service layer. For example, a hook that adds tenant filtering based on a JWT claim must ensure that the claim is strictly validated and not used to directly append raw user input into database queries. Without proper sanitization and schema validation, an attacker may provide specially crafted parameters that, when combined with token-derived values, bypass intended filters. This can lead to data exposure across tenants or privilege escalation when administrative claims are improperly trusted.
Real-world patterns include concatenating user input with field names derived from token metadata, constructing dynamic $where clauses, or using token-based role claims to conditionally include sensitive filters. If input validation is limited to presence checks rather than type and range checks, injection through nested objects or arrays becomes feasible. Even when using an ORM or query builder, improper merging of user-supplied data with token-derived defaults can reintroduce injection risks.
To detect such issues, scanning an API that relies on JWT tokens in FeathersJS should include checks that validate the handling of token claims, verify that service queries are constrained by strict schemas, and ensure that role-based filters are not bypassed via injected parameters. The scanner evaluates whether authentication checks are applied consistently and whether the API surface that depends on JWT data is tested for injection-style manipulations.
Jwt Tokens-Specific Remediation in Feathersjs
Remediation focuses on strict validation of JWT claims, disciplined query construction, and explicit authorization checks. Never directly trust payload fields to build queries or to decide access control. Use allowlists for values derived from tokens, and apply the same input validation rules to token-derived data as you would to client-supplied input.
Example of insecure usage:
// Insecure: directly using token payload to filter without validation
app.service('items').find({
query: {
userId: params.user.sub,
$where: 'true' // dynamically appended later
}
});
Instead, validate and constrain all inputs. Define a JSON schema for query parameters and ensure token-derived values conform to it. Use a consistent service hook to enforce ownership and tenant checks.
Secure example with validation and explicit ownership check:
const { Joi } = require('celebrate');
app.service('items').hooks({
before: {
all: [hook => {
// Validate query inputs against a strict schema
const querySchema = Joi.object({
$limit: Joi.number().integer().min(1).max(100).default(10),
$skip: Joi.number().integer().min(0).default(0),
userId: Joi.string().pattern(/^[a-f0-9]{24}$/).required(),
status: Joi.string().valid('active', 'archived', 'deleted').optional()
});
// Ensure params.user.sub is a valid ObjectId pattern
if (!/^[a-f0-9]{24}$/.test(hook.params.user.sub)) {
throw new Error('Unauthorized');
}
// Enforce ownership explicitly
hook.params.query.userId = hook.params.user.sub;
// Validate final query shape
const { error, value } = querySchema.validate(hook.params.query);
if (error) {
throw new Error('Invalid query');
}
hook.params.query = value;
return hook;
}]
}
});
When using JWTs for tenant isolation, incorporate tenant IDs as strict allowlisted values. Avoid building query strings by concatenating token fields with user input. Instead, map token claims to predefined roles or scopes and use them to gate access at the service or hook level.
Example of tenant-aware hook with strict claim validation:
app.service('records').hooks({
before: {
all: [async context => {
const allowedTenant = context.params.user.tenantId;
if (!allowedTenant || !/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(allowedTenant)) {
throw new Error('Invalid tenant');
}
// Ensure tenant filter is applied and cannot be overridden
context.params.query = context.params.query || {};
context.params.query.tenantId = allowedTenant;
// Prevent client from supplying tenantId
delete context.params.query.tenantId;
return context;
}]
}
});
For authorization, combine token claims with explicit policies. Do not rely solely on role claims to filter sensitive data. Use a permissions service that evaluates requests in context and ensures that operations respect the principle of least privilege.
Finally, ensure that any logging or error handling does not inadvertently expose token contents or internal query structures. Validate and sanitize outputs as well as inputs to reduce the impact of injection attempts.