Sql Injection in Feathersjs with Jwt Tokens
Sql Injection in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
SQL injection in a FeathersJS application using JWT tokens occurs when untrusted input is concatenated into SQL queries, even though authentication is enforced via a JWT. The presence of a valid JWT does not prevent SQL injection; it only confirms identity or permissions. If user-controlled data such as query parameters, headers, or request bodies are directly interpolated into SQL strings or passed to unsafe query builders, an attacker can manipulate the SQL logic. For example, an attacker authenticated with a legitimate JWT might supply a malicious query parameter like id=1 OR 1=1 that alters the meaning of a dynamically built query. FeathersJS services often rely on adapters (e.g., for Sequelize, TypeORM, or Knex), and if input validation is skipped or improperly configured, these adapters may forward raw input to the database. Because the scan tests unauthenticated attack surfaces, it can still detect injection points in endpoints that incorrectly trust JWT-authenticated contexts. Common patterns include using string concatenation or template literals to build queries, or dynamic query generation that incorporates user-supplied fields such as sort, filter, or select. The OWASP API Top 10 lists injection among the most critical API risks, and this combination highlights how authentication mechanisms alone do not mitigate injection flaws.
Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes
To remediate SQL injection in FeathersJS while using JWT tokens, ensure that all database interactions use parameterized queries or an ORM/query builder with built-in sanitization. Never directly interpolate user input into SQL strings, even when a JWT is present and verified. Apply strict input validation and schema checks using libraries such as Ajv, and configure FeathersJS hooks to sanitize and validate data before it reaches services. Below are concrete code examples illustrating secure practices.
Example 1: Using Knex with parameterized queries
const { authenticate } = require('@feathersjs/authentication');
const { express } = require('@feathersjs/express');
const knex = require('./knex-instance');
const app = express();
app.configure(authenticate());
app.use('/todos', {
async find(params) {
const { $select, $filter } = params.query;
let query = knex('todos');
if ($select) {
// Validate $select against an allowed list to avoid column injection
const allowedColumns = ['id', 'title', 'completed'];
const columns = $select.split(',').filter(col => allowedColumns.includes(col.trim()));
if (columns.length) query = query.select(columns);
}
if ($filter) {
try {
const filter = JSON.parse($filter);
if (filter.status) {
query = query.where('status', filter.status);
}
} catch (err) {
throw new Error('Invalid filter JSON');
}
}
return query;
}
});
module.exports = app;
Example 2: Using TypeORM with repositories and parameterized methods
const { authenticate } = require('@feathersjs/authentication');
const { express } = require('@feathersjs/express');
const { TypeORM } = require('@feathersjs/typeorm');
const { Todo } = require('./models');
const app = express();
app.configure(authenticate());
app.configure(TypeORM({
entities: [Todo],
options: { type: 'postgres', /* ... */ }
}));
app.use('/todos', {
async get(id, params) {
const todoRepository = params.app.get('sequelize').models.Todo.repository;
// Using parameterized query via repository
const todo = await todoRepository.findOneBy({ id: Number(id) });
if (!todo) {
throw new Error('Not found');
}
return todo;
}
});
module.exports = app;
Example 3: Validating and sanitizing input in a FeathersJS hook
const { iff, isProvider } = require('feathers-hooks-common');
const sanitizeAndValidate = context => {
const { data } = context;
if (data.id) {
// Ensure id is a positive integer
const id = Number(data.id);
if (!Number.isInteger(id) || id <= 0) {
throw new Error('Invalid id');
}
context.data.id = id;
}
return context;
};
module.exports = {
before: {
all: [iff(isProvider('external'), sanitizeAndValidate)],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
JWT-specific notes
- Always verify the JWT using a strong secret or public key and validate claims such as
expandiss. - Do not rely on JWT scopes or roles alone to enforce data-level permissions; combine with explicit input validation and parameterized queries.
- Ensure that JWTs are transmitted only over HTTPS and that tokens are not logged in a way that could expose sensitive data.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |