Feathersjs API Security
Feathersjs Security Posture
Feathersjs provides a solid foundation for building real-time APIs with minimal configuration, but its "batteries included" approach can create security blind spots. Out of the box, Feathersjs enables CORS for all origins, allows unauthenticated access to services, and exposes internal service methods through REST endpoints. While the framework offers authentication plugins and authorization hooks, these security layers must be explicitly configured — they're not enabled by default.
The framework's flexibility is both its strength and weakness. Feathersjs makes it trivial to spin up a working API, but this ease of use often leads developers to ship production applications without proper security hardening. Common patterns like automatically generating REST endpoints for all service methods can inadvertently expose sensitive operations if developers don't carefully audit their service configurations.
Top 5 Security Pitfalls in Feathersjs
1. Unrestricted Service Methods
Feathersjs automatically exposes all CRUD operations (find, get, create, update, patch, remove) as REST endpoints. Developers often forget to restrict methods that shouldn't be publicly accessible. A common mistake is leaving the 'remove' method enabled on user or payment service endpoints, allowing anyone to delete records.
2. Missing Authentication Hooks
Without authentication hooks, Feathersjs services are completely open. Developers sometimes add authentication at the route level but forget that Feathersjs also exposes a real-time WebSocket API that bypasses route middleware. This creates a second attack vector that's often overlooked.
3. Insecure Default CORS Configuration
The default Feathersjs CORS configuration allows requests from any origin with any HTTP method. This is fine for development but dangerous in production, especially for APIs handling sensitive data or authentication tokens.
4. BOLA/IDOR Vulnerabilities
Feathersjs' flexible querying can lead to Broken Object Level Authorization issues. Developers might allow clients to specify arbitrary query parameters without validating that users can only access their own data. For example, allowing query parameters like '?userId=123' without checking if the authenticated user owns that ID.
5. Exposed Internal Service Methods
Feathersjs services can have internal methods that shouldn't be exposed via REST. Methods like 'setup', 'teardown', or custom service methods might be accidentally exposed if not properly configured with 'dispatch' properties.
Security Hardening Checklist
Authentication & Authorizationconst authentication = require('@feathersjs/authentication');
app.configure(authentication());
app.service('users').hooks({
before: {
all: [authenticate('jwt')],
create: [hashPassword('password')]
}
});
Service Method Restrictionsapp.service('messages').hooks({
before: {
find: [authenticate('jwt'), queryAuthHook],
get: [authenticate('jwt'), queryAuthHook],
create: [authenticate('jwt'), validateCreate],
update: [authenticate('jwt'), ownerOnly],
patch: [authenticate('jwt'), ownerOnly],
remove: [authenticate('jwt'), ownerOnly]
});
CORS Securityconst cors = require('cors');
app.use(cors({
origin: ['https://yourdomain.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
}));
Query Parameter Validationconst { disallow, discard } = require('feathers-hooks-common');
app.service('messages').hooks({
before: {
find: [disallow(discarded, 'query.userId')],
get: [disallow(discarded, 'query.userId')]
}
});
Rate Limitingconst rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
Input Validationconst { validateSchema } = require('feathers-hooks-common');
const messageSchema = {
type: 'object',
required: ['text'],
properties: {
text: { type: 'string', maxLength: 1000 },
userId: { type: 'string' }
}
};
app.service('messages').hooks({
before: {
create: validateSchema(messageSchema),
patch: validateSchema(messageSchema, { partialUpdate: true })
}
});