Insecure Design in Feathersjs with Jwt Tokens
Insecure Design in Feathersjs with Jwt Tokens
Insecure design in a FeathersJS application that uses JWT tokens often stems from how authentication, authorization, and token handling are wired into the service architecture. FeathersJS is a framework that encourages modular services and hooks; when JWT authentication is added via @feathersjs/authentication and @feathersjs/authentication-jwt, insecure design decisions can expose the application to token misuse, privilege escalation, and unauthorized data access.
One common pattern is placing the JWT authentication hook globally without scoping it to only the routes that require authentication. If the hook is applied to all services indiscriminately, public-facing services can inadvertently enforce authentication but also expose token validation logic in ways that assist attackers. For example, an attacker can probe endpoints that should be public to learn whether token validation errors differ from validation success, enabling account enumeration or token introspection.
Another insecure design issue is the misuse of JWT payloads for authorization decisions without additional server-side checks. A Feathers service might trust a payload claim such as role or scope to allow access to administrative functions, but if the service does not re-validate the user’s permissions against a backend policy store, an attacker who obtains a valid token can escalate privileges by altering claims within the token if the token is not properly cryptographically protected or if weak signing keys are used.
Insecure design also appears in token lifecycle management. FeathersJS applications that do not implement token revocation mechanisms, short expiration times, or secure storage practices increase the window of exposure. For instance, if a JWT is issued with a long-lived expiration and the application lacks a mechanism to revoke compromised tokens, an attacker can use a stolen token for an extended period. Similarly, failing to set the HttpOnly, Secure, and SameSite attributes on cookies when using cookie-based storage enables cross-site scripting (XSS) and cross-site request forgery (CSRF) attacks that compromise tokens.
Design-time decisions around error handling further weaken security. If FeathersJS services return detailed authentication errors, such as distinguishing between an invalid token, an expired token, or a missing token, attackers can use these messages to refine their attacks. A poorly designed authentication flow might also log tokens or sensitive claims in application logs or expose stack traces that include token material, increasing the risk of accidental data leakage.
Finally, integrating third-party authentication strategies without strict validation can introduce insecure defaults. For example, enabling multiple authentication strategies in FeathersJS without carefully ordering and scoping them may allow an attacker to bypass JWT validation by falling back to a weaker strategy. An insecure design fails to explicitly define which authentication type is required for each service and does not enforce strict transport security, leaving tokens vulnerable to interception or replay.
Jwt Tokens-Specific Remediation in Feathersjs
Remediation focuses on strict service configuration, token validation, and secure handling within FeathersJS. Use the built-in authentication hooks with explicit configuration, ensure proper token validation, and enforce least-privilege access through service hooks and custom logic.
Secure JWT Setup Example
Configure authentication explicitly and apply the JWT hook only where needed. Below is a secure initialization example that sets up JWT authentication with a strong secret and short expiration, and configures a protected service.
// src/authentication.js
const { Authentication } = require('@feathersjs/authentication');
const { JWTStrategy } = require('@feathersjs/authentication-jwt');
const authentication = new Authentication({
secret: process.env.AUTH_SECRET || 'a_very_strong_random_secret_here',
strategies: ['jwt'],
path: '/authentication',
service: 'authentication',
entity: 'user',
multi: false
});
// Expose authentication hooks
module.exports = {
authentication: authentication.hooks({
before: {
create: [
authentication.hooks.authenticate(['jwt'])
],
remove: [authentication.hooks.authenticate(['jwt'])]
}
})
};
In the above, the secret should be a long, randomly generated string stored in environment variables. Using a weak or default secret undermines the integrity of the token.
Protecting Services with Scoped Authentication
Apply JWT authentication on a per-service basis using hooks rather than globally. This ensures that only intended services require a valid token.
// src/services/messages/messages.hooks.js
const { iff, isProvider } = require('@feathersjs/hooks-common');
const { authenticate } = require('@feathersjs/authentication').hooks;
module.exports = {
before: {
all: [
iff(isProvider('external'), authenticate('jwt'))
],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
after: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
The iff(isProvider('external'), authenticate('jwt')) pattern restricts JWT validation to external requests only, preserving internal or test access patterns when needed. This scoped approach is an example of secure design.
Validating Token Claims and Enforcing Authorization
Do not rely solely on JWT payload claims for authorization. Re-validate permissions inside service hooks or custom logic. Below is an example hook that checks a user role claim and ensures the requesting user is allowed to modify a record.
// src/services/articles/articles.hooks.js
const { iff, isProvider } = require('@feathersjs/hooks-common');
const { authenticate } = require('@feathersjs/authentication').hooks;
module.exports = {
before: {
all: [authenticate('jwt')],
get: [
async context => {
const { user } = context.params;
const { id } = context.params.query;
const record = await context.app.service('articles').get(id);
if (record.userId !== user.userId && user.role !== 'admin') {
throw new Error('Not authorized');
}
return context;
}
],
find: [
async context => {
const { user } = context.params;
// Apply additional filters so users only see their own data unless admin
if (user.role !== 'admin') {
context.params.query.$or = [
{ userId: user.userId },
{ isPublic: true }
];
}
return context;
}
],
create: [],
update: [],
patch: [],
remove: []
}
};
Ensure tokens are transmitted securely. When using cookies, configure the service to expect secure cookies and set appropriate attributes.
// src/authentication.js additions for cookie transport
const authentication = new Authentication({
secret: process.env.AUTH_SECRET,
strategies: ['jwt'],
path: '/authentication',
service: 'authentication',
entity: 'user',
multi: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 86400000 // 1 day
}
});
Implement token revocation by maintaining a denylist (e.g., stored in a fast data store) and checking it on each request via a custom hook or service. Shorten token lifetimes and use refresh tokens with strict rotation policies to reduce the impact of token theft.
Finally, enforce transport security by requiring HTTPS in production and validating issuer and audience claims in the JWT verification step. Avoid logging tokens or sensitive user data, and standardize error responses to avoid leaking whether a token was malformed, expired, or valid.