HIGH phishing api keysfeathersjsjwt tokens

Phishing Api Keys in Feathersjs with Jwt Tokens

Phishing Api Keys in Feathersjs with Jwt Tokens — how this combination creates or exposes the vulnerability

FeathersJS is a framework for creating JavaScript APIs quickly. When JWT tokens are used for authentication, developers often store tokens in client-side code, local storage, or cookies. If an attacker can phish an API key or token value, they can impersonate users across services that trust the same JWT signing key.

In FeathersJS, authentication plugins commonly issue JWTs after verifying credentials. A typical setup uses feathers-authentication and @feathersjs/authentication-jwt. Misconfigured CORS, missing Secure and HttpOnly flags, or token leakage through logs or error messages can expose JWTs. Phishing campaigns that target API keys or session cookies may capture these JWTs and use them to call unprotected endpoints.

Consider an endpoint that relies solely on the JWT for authorization without additional scope or ownership checks:

// server.js
const authentication = require('@feathersjs/authentication');
const jwt = require('@feathersjs/authentication-jwt');
const app = require('./app');

app.configure(authentication({
  secret: 'super-secret-key'
}));
app.configure(jwt());

If a phishing site tricks a user into visiting a malicious page, JavaScript running in the browser may read the JWT from local storage and send it to the attacker. The attacker can then use the token to call FeathersJS APIs that do not enforce additional authorization checks, effectively reusing the phished credentials.

Another vector involves phishing API keys used to call third-party services. If a FeathersJS service uses API keys as bearer tokens and those keys are embedded in client-side bundles or transmitted over insecure channels, attackers can harvest them and replay requests. Because JWTs often carry identity claims, a phished token can lead to privilege escalation if the token contains elevated scopes that are not validated on each request.

SSRF and external phishing pages can also expose JWTs if the application reflects tokens in error messages or redirects. For example, an open redirect that includes the token in a URL parameter can be captured by an attacker monitoring query strings.

To detect such issues, scanning tools test whether JWTs are transmitted insecurely, whether tokens are stored in accessible locations, and whether authorization checks consistently validate token ownership and scopes. Findings typically highlight missing token binding, weak CORS rules, and lack of token revocation mechanisms.

Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on secure token handling, strict authorization, and reducing the impact of leaked JWTs. Use HttpOnly, Secure cookies for token storage, enforce strict CORS, and validate ownership on every request.

1. Store JWTs in HttpOnly, Secure cookies instead of local storage to mitigate theft via XSS.

// server.js — cookie-based JWT configuration
const authentication = require('@feathersjs/authentication');
const jwt = require('@feathersjs/authentication-jwt');
const cookieParser = require('cookie-parser');

app.use(cookieParser());

app.configure(authentication({
  secret: 'super-secret-key',
  tokenOptions: {
    expiresIn: '1d',
    secure: true,
    httpOnly: true,
    sameSite: 'strict'
  }
}));

app.configure(jwt({
  cookie: {
    name: 'feathers-jwt',
    secure: true,
    httpOnly: true,
    sameSite: 'strict',
    path: '/'
  }
}));

2. Enforce scope and resource-level checks on endpoints. Do not rely on JWT payload alone to authorize actions.

// services/messages/messages.class.js
class MessageService {
  async find(params) {
    const { user } = params;
    // Ensure the requesting user can only access their own messages
    return this.app.service('messages').Model.query(qb =>
      qb.where('user_id', user.id)
    );
  }

  async get(id, params) {
    const { user } = params;
    const message = await this.app.service('messages').Model.findByPk(id);
    if (!message || message.user_id !== user.id) {
      throw new Error('Not found');
    }
    return message;
  }
}

3. Validate token binding by including a per-request fingerprint, such as a hashed user-agent or IP, and verify it on sensitive operations.

// server.js — token binding example
app.hooks({
  before: {
    create: [context => {
      const { tokenVerifyClaims } = context.params;
      const expectedFingerprint = hashUserContext(context);
      if (tokenVerifyClaims.fingerprint !== expectedFingerprint) {
        throw new Error('Token binding mismatch');
      }
      return context;
    }]
  }
});

function hashUserContext(context) {
  const ua = context.headers['user-agent'] || '';
  const ip = context.connection.remoteAddress || '';
  return crypto.createHash('sha256').update(ua + ip).digest('hex');
}

4. Rotate signing keys regularly and implement token revocation via a denylist stored in a fast store (e.g., Redis). Configure short token lifetimes and use refresh tokens with strict validation.

// server.js — short-lived JWTs and refresh token flow
app.configure(authentication({
  secret: 'super-secret-key',
  tokenOptions: {
    expiresIn: '15m',
    refreshExpiresIn: '7d'
  }
}));

5. Tighten CORS and avoid exposing tokens to cross-origin contexts that could be phished.

// server.js — strict CORS
const cors = require('@koa/cors');
app.use(cors({
  origin: 'https://your-trusted-domain.com',
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Authorization', 'Content-Type'],
  credentials: true
}));

Frequently Asked Questions

What should I do if a JWT token is leaked through error messages?
Ensure error responses do not include raw JWTs or stack traces. Use generic error messages and configure logging to redact tokens. Rotate signing keys and revoke affected sessions immediately.
How can I verify token binding in FeathersJS services?
Attach a fingerprint derived from request context (e.g., hashed user-agent and IP) to the JWT claims and validate it in hooks or service methods before processing sensitive operations.