Session Fixation in Feathersjs with Hmac Signatures
Session Fixation in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application assigns a user a session identifier before authentication and does not rotate or validate it after login. In FeathersJS applications that use Hmac Signatures for stateless authentication (typically via JWT or similar mechanisms), fixation risk shifts from traditional session cookies to the claims and key material used to generate and verify signatures.
FeathersJS itself is framework-agnostic about transport and storage, so developers often pair it with transports like Socket.io or REST hooks. When Hmac Signatures are used, the client may initially make unauthenticated requests that include a predictable or static identifier (e.g., a client-generated key ID or an unverified payload field). If the server uses this pre-authentication identifier as part of the Hmac payload without strict validation, an attacker can fixate a known value and later reuse it after the victim authenticates.
Consider an endpoint that accepts a clientToken in headers or query parameters before authentication. If the server includes this clientToken in the data signed by the Hmac key—without first verifying that the token is tied to an authenticated context—an attacker can set clientToken=attackerChosen. After the victim logs in, the server signs the same attacker-chosen token, allowing the attacker to replay the signed Hmac in subsequent requests. This bypasses the assumption that only the authenticated party can produce valid signatures.
Additionally, Hmac signature schemes require careful handling of nonce, timestamp, and key rotation. In FeathersJS, if nonces or timestamps are not enforced or are reused, an attacker can replay a fixed signed request. For example, a static nonce embedded in the signed string defeats the purpose of replay protection. The server must validate that each Hmac payload includes a fresh nonce or timestamp and that it has not been seen before, even before identity verification.
Another vector involves misconfigured service hooks where unauthenticated requests can influence authenticated behavior. If a FeathersJS service allows unauthenticated calls to update a resource that later participates in Hmac generation (e.g., modifying a shared secret or a user-specific salt), an attacker can manipulate the context used to create signatures. This is especially relevant when using external key stores or environment variables that may be indirectly influenced by unauthenticated inputs.
Real-world patterns mirror known vulnerabilities such as CVE-2020-15168 (in which improper nonce validation led to signature replay) and align with OWASP API Top 10:2023 Broken Object Level Authorization (BOLA) and Security Misconfiguration. The root cause is treating Hmac Signatures as a simple integrity check while neglecting identity binding and replay safeguards.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on ensuring that Hmac payloads are bound to authenticated identity, that nonces and timestamps are validated, and that pre-authentication inputs never influence signature generation.
Example 1: Binding Hmac to authenticated context
Only sign data after successful authentication, and include a user-specific stable identifier (e.g., user ID) in the signed payload. Do not include client-controlled values that exist before login.
const crypto = require('crypto');
function generateHmac(userId, timestamp, nonce, secret) {
const payload = JSON.stringify({
sub: userId,
iat: timestamp,
nonce: nonce
});
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}
// In a FeathersJS hook after authentication
app.service('todos').hooks({
before: {
create(context) {
const { user } = context.params;
const timestamp = Math.floor(Date.now() / 1000);
const nonce = require('crypto').randomBytes(16).toString('hex');
const signature = generateHmac(user._id, timestamp, nonce, process.env.HMAC_SECRET);
context.params.headers = {
...context.params.headers,
'x-auth-timestamp': timestamp,
'x-auth-nonce': nonce,
'x-auth-signature': signature
};
return context;
}
}
});
Example 2: Server-side validation with replay protection
The server must verify timestamp, nonce uniqueness, and signature integrity before processing the request. Use a short window for timestamps and a cache for nonces to prevent reuse.
const crypto = require('crypto');
const nonceCache = new Set(); // In production, use a distributed TTL cache
function verifyHmac(req, secret) {
const timestamp = parseInt(req.headers['x-auth-timestamp'], 10);
const nonce = req.headers['x-auth-nonce'];
const receivedSignature = req.headers['x-auth-signature'];
if (!timestamp || !nonce || !receivedSignature) {
throw new Error('Missing authentication headers');
}
const now = Math.floor(Date.now() / 1000);
const window = 5; // seconds
if (Math.abs(now - timestamp) > window) {
throw new Error('Timestamp outside acceptable window');
}
if (nonceCache.has(nonce)) {
throw new Error('Nonce already used');
}
nonceCache.add(nonce);
// In production, prune cache periodically
const payload = JSON.stringify({
sub: req.userId, // resolved after auth
iat: timestamp,
nonce: nonce
});
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature))) {
throw new Error('Invalid signature');
}
return true;
}
// In a FeathersJS hook before
app.service('todos').hooks({
before: {
all: [function contextVerifier(context) {
if (context.params.headers && context.params.headers['x-auth-signature']) {
verifyHmac(context.params, process.env.HMAC_SECRET);
// Ensure userId is set from verified identity, not from untrusted input
context.params.userId = context.params.user._id;
}
return context;
}]
}
});
Example 3: Avoiding pre-authentication influence on Hmac
Ensure that any data used in Hmac generation is set only after identity is established. Reject or ignore client-provided identifiers that could be fixed before login.
app.service('auth').hooks({
before: {
create(context) {
// Do not use context.data.clientToken in any signing logic
// Instead, after successful auth, generate fresh Hmac-bound values
return context;
}
},
after: {
create(context) {
const userId = context.result.userId;
const timestamp = Math.floor(Date.now() / 1000);
const nonce = require('crypto').randomBytes(16).toString('hex');
const signature = generateHmac(userId, timestamp, nonce, process.env.HMAC_SECRET);
return {
...context.result,
meta: {
timestamp,
nonce,
signature
}
};
}
}
});
These practices align with OWASP ASVS and the API Security Verification Standard, ensuring that Hmac Signatures provide integrity and authenticity without enabling fixation. For teams using managed solutions, the middleBrick CLI can scan for missing nonce validation and improper binding in Hmac flows, while the GitHub Action can enforce security gates in CI/CD pipelines.