Broken Access Control in Feathersjs with Jwt Tokens
Broken Access Control in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Broken Access Control in a FeathersJS application using JWT tokens typically occurs when authorization checks are missing, incomplete, or incorrectly tied to the token payload. FeathersJS is a framework-agnostic REST and real-time API layer; it does not enforce authorization by default. Developers must explicitly add service hooks and authentication middleware to enforce scope-based or role-based rules. Relying only on JWT presence, without validating scopes or roles embedded in the token, creates a BOLA/IDOR exposure where one authenticated user can act on another user’s resources by guessing or enumerating identifiers.
JWT tokens can carry claims such as roles, scopes, or user identifiers. If the API endpoints do not validate these claims per request, an attacker with a valid but low-privilege token can perform privilege escalation or access other users’ data. For example, a token issued for read-only analytics might be reused to invoke admin-only services if the service does not verify scope claims like read:reports vs write:reports. MiddleBrick’s BOLA/IDOR and BFLA/Privilege Escalation checks are designed to detect these authorization gaps by correlating token claims with runtime behavior and OpenAPI definitions.
FeathersJS applications often integrate with authentication libraries that verify JWT signatures and attach a user object to the request. However, if the authorization logic is implemented as generic hooks that trust the user object without cross-checking service-specific policies, the attack surface expands. Consider a user profile service where the route is /users/:id; if the hook does not assert that the authenticated user’s ID matches params.user._id, an attacker can modify the URL to access or modify another profile. JWT tokens may also contain tenant or organization identifiers; failing to enforce tenant-level scoping enables cross-tenant data leakage, a common multi-tenant misconfiguration.
OpenAPI/Swagger analysis helps identify whether paths correctly reference security schemes and whether operations define required scopes. MiddleBrick scans unauthenticated attack surfaces and maps findings to frameworks such as OWASP API Top 10 A01:2023 — Broken Access Control. Real-world examples include missing required: true on security schemes in the spec or runtime endpoints that skip role/scope validation. Continuous monitoring and CI/CD integration, such as the GitHub Action, can alert when a new service lacks appropriate authorization checks before deployment.
Remediation involves explicit authorization at the service hook level, validating JWT claims on every request, and ensuring least privilege. Use role or scope checks that match the token’s embedded permissions, and avoid relying on client-supplied identifiers alone. The Pro plan’s continuous monitoring can track these controls across API versions, and the CLI can integrate into scripts to verify that authorization logic is present and consistent with the spec.
Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes
Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes
To remediate Broken Access Control when using JWT tokens in FeathersJS, enforce authorization in service hooks and validate token claims explicitly. Below are concrete, working examples that demonstrate secure patterns.
First, ensure your authentication setup verifies the JWT and attaches a user with claims such as roles and scopes:
// src/authentication.js
const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication-jwt');
const app = require('./app');
app.use('/authentication', new AuthenticationService({
entity: 'user',
service: app.service('authentication'),
jwt: {
secret: process.env.JWT_SECRET,
algorithms: ['HS256']
}
}));
app.configure(authentication({
secret: process.env.JWT_SECRET,
strategies: ['jwt'],
entity: 'user',
cookie: false
}));
Next, add a service hook that validates roles or scopes from the token before allowing access. For a reports service where scopes matter:
// src/hooks/authorization.js
const { iff, isProvider } = require('feathers-hooks-common');
const requireScope = (requiredScope) => (context) => {
const { user } = context.params;
if (!user || !user.scopes) {
throw new Error('Unauthorized');
}
if (!user.scopes.includes(requiredScope)) {
throw new Error('Insufficient scope');
}
return context;
};
const requireRole = (allowedRoles) => (context) => {
const { user } = context.params;
if (!user || !user.role) {
throw new Error('Unauthorized');
}
if (!allowedRoles.includes(user.role)) {
throw new Error('Forbidden role');
}
return context;
};
module.exports = {
before: {
all: [iff(isProvider('external'), requireScope('read:reports'))],
find: [iff(isProvider('external'), requireRole(['admin', 'analyst']))],
get: [iff(isProvider('external'), requireScope('read:reports'), requireRole(['admin', 'analyst']))],
create: [iff(isProvider('external'), requireScope('write:reports'))],
update: [iff(isProvider('external'), requireScope('write:reports'), requireRole(['admin']))],
patch: [iff(isProvider('external'), requireScope('write:reports'), requireRole(['admin']))],
remove: [iff(isProvider('external'), requireRole(['admin']))]
}
};
Apply the hook to your service:
// src/services/reports/reports.class.js
const { Service } = require('feathersjs');
const authorizationHooks = require('../hooks/authorization');
class ReportsService extends Service {
// Service logic
}
module.exports = function () {
const app = this;
const reportsService = new ReportsService({
name: 'reports',
paginate: { default: 10, max: 50 }
});
app.use('/reports', reportsService);
const service = app.service('reports');
service.hooks(authorizationHooks);
};
For tenant-scoped data, validate the tenant ID from the token against the resource:
// src/hooks/tenant-scoped-authorization.js
const tenantScope = (context) => {
const { user, id } = context.params;
// Assuming user.tenantId is from JWT and resource has tenantId
if (id && context.result && context.result.tenantId) {
if (String(user.tenantId) !== String(context.result.tenantId)) {
throw new Error('Cross-tenant access denied');
}
}
return context;
};
module.exports = {
before: {
get: [tenantScope],
patch: [tenantScope],
remove: [tenantScope]
}
};
These examples ensure that JWT claims are evaluated on each request and that operations are gated by role or scope. Combine these hooks with OpenAPI checks and continuous monitoring to reduce the risk of Broken Access Control. The CLI can validate that hooks exist for sensitive routes, and the Web Dashboard can track changes over time.