Man In The Middle in Feathersjs with Jwt Tokens
Man In The Middle in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for real-time APIs that commonly uses JWT tokens for stateless authentication over HTTP and WebSocket transports. A Man In The Middle (MitM) vulnerability occurs when JWT tokens are transmitted or stored without adequate transport and application-layer protections, enabling an attacker on the network path or application layer to intercept or tamper with tokens.
Without HTTPS, tokens sent in URLs or custom headers can be captured via packet sniffing or compromised proxies. FeathersJS channels including sockets can be downgraded or intercepted if the initial handshake is unauthenticated. Attack patterns such as SSRF or insecure service mesh routing may expose internal endpoints where JWTs are passed in headers or query strings, increasing exposure. If tokens are not bound to a specific channel or origin, an intercepted token can be reused across sessions (replay). In systems relying on opaque tokens or weak signature verification, an attacker who gains access to a token can escalate privileges or impersonate users across Feathers services.
Additionally, misconfigured service hooks and transports can leak JWTs in logs, error messages, or through event emissions in real-time channels. Without per-channel authentication or message integrity checks, an attacker who positions themselves between client and server can inject malicious events or observe sensitive data. Even when JWTs are cryptographically signed, failure to validate issuer, audience, and token binding allows an attacker to exploit stolen tokens within the FeathersJS application graph.
Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on enforcing HTTPS, strict token validation, and minimizing token exposure in transports. Always terminate TLS at the edge and enforce secure cookies or authorization headers with SameSite and Secure flags for web clients. For FeathersJS, configure transports and hooks to validate JWTs rigorously and avoid leaking tokens in URLs or logs.
Example 1: HTTPS enforcement and secure transport configuration
// src/app.js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
// Enforce HTTPS in production by redirecting or rejecting insecure origins
app.set('forceHTTPS', true);
// Configure express to use secure cookies if using cookie-based JWT storage
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// Use only secure, httpOnly cookies for browser clients
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Apply hooks that validate JWTs on all service routes
app.configure(express.rest());
app.configure(express.socketio());
module.exports = app;
Example 2: JWT validation hook with audience and issuer checks
// src/hooks/authentication.js
const { AuthenticationError } = require('@feathersjs/errors');
const jwt = require('jsonwebtoken');
module.exports = function authentication(options = {}) {
return async context => {
const { headers } = context.params;
const token = headers.authorization || (context.params.query && context.params.query.access_token');
if (!token) {
throw new AuthenticationError('Access token required');
}
// Use environment variables for secrets and enforce audience/issuer
const publicKey = process.env.JWT_PUBLIC_KEY;
const audience = process.env.JWT_AUDIENCE || 'feathers-api';
const issuer = process.env.JWT_ISSUER || 'https://auth.example.com';
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
audience,
issuer,
clockTolerance: 60
});
// Attach verified payload to context, avoiding token leakage
context.params.user = decoded;
delete context.params.headers.authorization; // prevent accidental logging
return context;
} catch (error) {
throw new AuthenticationError('Invalid token');
}
};
};
Example 3: Securing WebSocket channels against token replay
// src/hooks/socket-authentication.js
module.exports = function socketAuthentication(options = {}) {
return async context => {
const { app, channel, socket } = context.params;
// channel contains connection metadata; ensure token is validated per connection
const token = socket.request.headers.authorization;
if (!token) {
throw new Error('Unauthorized socket connection');
}
const jwt = require('jsonwebtoken');
const publicKey = process.env.JWT_PUBLIC_KEY;
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
audience: 'feathers-sockets',
issuer: 'https://auth.example.com'
});
// Bind token to this socket channel to prevent reuse across connections
channel.context = channel.context || {};
channel.context.user = decoded;
channel.context.tokenId = decoded.jti; // use JWT ID for replay prevention
return context;
} catch (error) {
throw new Error('Socket authentication failed');
}
};
};
Example 4: Avoiding token leakage in logs and errors
// src/hooks/sanitize-headers.js
module.exports = function sanitizeHeaders(options = {}) {
return context => {
// Remove authorization header from params sent to services/hooks
if (context.params.headers && context.params.headers.authorization) {
// Keep a hashed reference if needed for auditing, but never log raw tokens
const token = context.params.headers.authorization;
context.params.meta = context.params.meta || {};
context.params.meta.tokenHash = require('crypto').createHash('sha256').update(token).digest('hex');
delete context.params.headers.authorization;
}
return context;
};
};
Example 5: Per-service authorization checks
// src/services/items/hooks/authorize.js
module.exports = function authorize(options = {}) {
return async context => {
const user = context.params.user;
if (!user || !user.scopes || !user.scopes.includes('read:items')) {
throw new Error('Unauthorized: insufficient scope');
}
// Apply property-level checks using user roles embedded in JWT
if (context.method === 'find' && user.roles && user.roles.includes('admin')) {
// Admin sees all; otherwise filter by organization_id
if (!context.params.query.$or) {
context.params.query.$or = [];
}
context.params.query.$or.push({ organization_id: user.organization_id });
}
return context;
};
};
Example 6: MiddleBrick integration for continuous verification
Use the CLI or Dashboard to validate that your endpoints enforce HTTPS and reject unauthenticated requests. Run middlebrick scan <url> to confirm findings such as missing transport security or overly permissive CORS. The GitHub Action can fail builds if risk scores degrade, ensuring JWT handling remains robust across deployments.