HIGH man in the middlefeathersjsjwt tokens

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.

Frequently Asked Questions

How does FeathersJS WebSocket authentication interact with JWT tokens in a MitM scenario?
FeathersJS sockets transmit JWTs via initial handshake headers; without per-channel validation and HTTPS, tokens can be intercepted and reused. Secure each socket channel by verifying tokens in hooks and binding the token payload (e.g., jti) to the connection to prevent replay.
Can JWT public keys be rotated safely in a FeathersJS deployment?
Yes, but you must implement key rotation logic that accepts a JWKS endpoint or a key identifier (kid) in the token header. Validate audience and issuer on every request and ensure hooks reject tokens signed with outdated keys to avoid token misuse during rotation.