FeathersJS service exposes data publicly when authentication before hook is omitted
0 reputation · 03 Mar 2024, 13:25 UTC
0 reputation · 03 Mar 2024, 13:25 UTC
Goal: Confirm that a FeathersJS service protected by a before hook that validates the presence of params.user will reject requests lacking a valid authentication token, preventing unintended public exposure of data.
Uncertainty: It is unclear whether the authentication middleware reliably sets params.user before the hook executes, and whether placing the validation hook after a data‑fetching hook could still allow data to leak. Additionally, there is ambiguity about how to verify that every sensitive service has the hook registered in the correct order without introducing false positives.
1. Does the authentication middleware guarantee params.user is defined only for authenticated requests? Yes. The @feathersjs/authentication middleware (or the authenticate() hook) sets params.user only after successful token validation. When validation fails or no token is provided, params.user remains undefined.
2. If a validation hook runs after a service-method hook, can data leak? Yes. Hooks execute in registration order. A before hook that fetches data (e.g., a custom find hook) registered before the authentication check will run and return data to an unauthenticated client.
3. Minimal configuration to enforce authentication on every sensitive service? Register the authentication middleware globally, then add a single shared before hook on app.all (or each service's all method) that throws when params.user is falsy. This hook must be registered before any other before hooks that access data.
FeathersJS processes before hooks sequentially. The authentication check must be the first before hook so it can short-circuit the chain. Example of correct ordering:
// app.js or a central hooks file
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider } = require('feathers-hooks-common');
// 1. Global authentication middleware (Express/Koa)
app.configure(authentication());
// 2. Shared guard hook — runs before ANY service method
app.hooks({
before: {
all: [
authenticate('jwt'), // sets params.user or throws 401
// other global before hooks go AFTER this line
],
},
});
// 3. Service-specific hooks (if any) register AFTER the guard
app.service('messages').hooks({
before: {
find: [someDataHook], // safe: runs only after auth passes
},
});
If you instead write:
app.service('messages').hooks({
before: {
find: [someDataHook, authenticate('jwt')], // DANGEROUS: dataHook runs first
},
});
…an unauthenticated request executes someDataHook and receives data before the 401 is thrown.
Authorization header. Expect HTTP 401 and an empty response body.console.log('auth check', !!context.params.user). Confirm it logs false for unauthenticated requests.undefined, asserting the service behaves correctly in both cases.Are you using the built-in authenticate() hook, a custom guard, or a third-party authentication strategy? The exact hook name changes the registration snippet but not the ordering principle.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.