Identification Failures in Feathersjs with Api Keys
Identification Failures in Feathersjs with Api Keys — how this combination creates or exposes the vulnerability
Identification failures occur when an API fails to accurately determine and enforce the identity of the caller. In FeathersJS, a common pattern is to use API keys for access control, but misconfiguration can turn this into an authorization bypass or information leak. FeathersJS itself is framework-agnostic about authentication; it relies on hooks and services to manage identity. If API key validation is implemented only at the transport layer or in a lightweight hook that does not consistently apply across services, an attacker can make requests that bypass intended access restrictions.
Consider a FeathersJS application that uses a custom hook to check an apiKey header against a database of allowed keys. If this hook is not applied to all relevant service routes, or if it is placed after service-side find or get logic, an unauthenticated request may reach the service handler. Because FeathersJS services expose REST-like endpoints by default, a missing or misordered hook can allow an attacker to enumerate records they should not see, especially when the service uses soft-deletes or relies on query parameters for filtering.
Another vector specific to API keys is key leakage through error messages or logs. If a FeathersJS hook rejects a request due to an invalid or missing API key and returns a verbose error, it may confirm the existence of a valid key pattern or reveal internal service behavior. Additionally, if the API key is passed in headers and the service inadvertently echoes headers or includes them in logs or trace outputs, keys may be exposed to unauthorized parties or log aggregation systems.
Identification failures also arise when API keys are accepted via multiple inputs (e.g., header and query parameter) without consistent validation. In FeathersJS, if a hook checks params.headers['x-api-key'] but an attacker can also supply the key as params.query.api_key, and only one path is secured, the unsecured path becomes a bypass. This becomes critical in chained hook scenarios where one hook validates and another inadvertently trusts a different input source.
Moreover, if the application uses service mixins or patches that modify queries, an API key intended for identification might be inadvertently ignored when constructing filters. For example, a mixin that appends a $eq filter on userId may not account for requests where the caller is identified only by API key, leading to data exposure across tenants or users. Without explicit scoping in the hook that resolves the API key to a tenant or user context, the service may return data belonging to other identities.
Api Keys-Specific Remediation in Feathersjs — concrete code fixes
To remediate identification failures when using API keys in FeathersJS, ensure consistent validation across all service routes and strict separation of identification from query parameters. Centralize key validation in a single hook that runs before any service method and apply it universally. Do not rely on query parameters for key transmission; prefer headers to reduce leakage risk.
Below is a secure example of an API key validation hook in a FeathersJS application using hooks v5 and a generic key store (e.g., database or KV). This hook checks the presence and validity of the x-api-key header, resolves the associated application scope, and attaches a normalized context for downstream services.
// src/hooks/api-key-auth.js
module.exports = function apiKeyAuth(options = {}) {
return async context => {
const { headers } = context.params;
const apiKey = headers && headers['x-api-key'];
if (!apiKey || typeof apiKey !== 'string') {
throw new Error('Unauthorized: Missing API key');
}
// Example: lookup key from a KV store or database
const keyRecord = await context.app.service('api-keys').get(apiKey, {
query: { $select: ['id', 'scope', 'enabled'] }
}).catch(() => null);
if (!keyRecord || !keyRecord.enabled) {
throw new Error('Unauthorized: Invalid or disabled API key');
}
// Attach resolved identity to context for services and downstream hooks
context.params.authIdentity = {
type: 'api-key',
keyId: keyRecord.id,
scope: keyRecord.scope
};
// Ensure query parameters cannot override identification
delete context.params.query.api_key;
return context;
};
};
Apply this hook globally in your FeathersJS configuration so that it runs for all services. Below is an example of registering the hook in src/app.js.
const apiKeyAuth = require('./hooks/api-key-auth');
app.configure(expressRest());
app.configure(socketio());
// Apply globally before any service hook
app.hooks({
before: {
all: [apiKeyAuth()],
// Service-specific overrides can be added per service if needed
},
after: {
all: []
},
error: {
all: []
}
});
Additionally, enforce that services do not rely on query parameters for identification. In your service schemas and route definitions, avoid accepting API keys as query fields. If you must support multiple authentication schemes, ensure each scheme is validated independently and that the resolved identity is used consistently in data access filters. Below is a safe service definition that expects the identity to be pre-resolved by hooks and uses it to scope queries.
// src/services/messages/messages.class.js
const { Service } = require('feathers-sequelize');
class MessagesService extends Service {
async find(params) {
// Use the identity attached by the authentication hook
const { authIdentity } = params;
if (!authIdentity || authIdentity.type !== 'api-key') {
throw new Error('Unauthorized');
}
// Scope the query to the tenant or application associated with the key
return super.find({
...params,
paginate: false,
where: {
scopeId: authIdentity.scope
}
});
}
}
module.exports = MessagesService;
Finally, avoid returning detailed errors that confirm the presence or format of valid keys. Standardize error responses to a generic unauthorized message in production and ensure headers are not echoed in responses or logs. Combine these practices with regular key rotation and scope minimization to reduce the impact of a leaked key.