Clickjacking in Feathersjs with Jwt Tokens
Clickjacking in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an invisible or disguised page element tricks a user into performing unintended actions. In a FeathersJS application that uses JWT tokens for authentication, the presence of JWTs does not prevent clickjacking; it only changes how authentication is carried out. If your FeathersJS app embeds authenticated pages or admin panels inside frames without explicit anti-clickjacking controls, an attacker can overlay invisible iframes or form elements on top of legitimate UI components, such as sensitive operation buttons or settings forms. Because the browser automatically sends cookies and the JWT (for example via Authorization headers stored by the client) to the embedded origin, the forged request executes with the victim’s permissions.
Consider a FeathersJS service that renders an admin settings page and relies on JWTs passed in the Authorization header. If this page is reachable inside an iframe and the server does not set Content-Security-Policy: frame-ancestors or X-Frame-Options, an attacker can host a page that embeds https://api.example.com/admin/settings under a transparent layer of UI controls. A user logged into your app who visits the attacker’s site may unknowingly submit settings changes or perform administrative actions, with the JWT being sent automatically by the browser. The JWT itself is not leaked directly via clickjacking, but the authenticated session is abused because the browser attaches it to the forged request. This becomes particularly risky when JWTs have long lifetimes or when the app exposes sensitive state-changing endpoints without additional authorization checks per request.
FeathersJS applications commonly expose REST or Socket.io endpoints that perform create, update, and delete operations. If these endpoints depend solely on token validity and lack per-request authorization and idempotency-safe operations, an attacker can chain clickjacking with other techniques such as CSRF on non-JWT-based authentication flows or social engineering to increase impact. Even when JWTs are stored in headers and not cookies, browsers still send credentials for embedded requests if the server allows framing. Therefore, mitigating clickjacking in a FeathersJS + JWT setup requires explicit frame-ancestor policies and secure embedding rules, not just token handling improvements.
Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on two layers: server-side framing policies and secure handling of JWTs in FeathersJS. You must prevent your app from being embedded in iframes and ensure JWTs are handled in a way that does not encourage insecure practices. Below are concrete, working examples for a FeathersJS application.
1) Set strict anti-framing headers
In your FeathersJS server setup, add middleware that sets Content-Security-Policy with frame-ancestors and X-Frame-Options. This ensures browsers refuse to render your pages inside untrusted frames.
// src/middleware/index.js
// Example: CSP and X-Frame-Options for FeathersJS
const csp = require('helmet-csp');
app.configure(() => {
// Other Feathers configuration...
// Use helmet-csp to set Content-Security-Policy
app.use(csp({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // adjust as needed
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"],
frameAncestors: ["'none'"], // Critical: disallow all framing
baseUri: ["'self'"],
// Ensure older browsers respect framing restrictions
frameOptions: 'deny' // Sets X-Frame-Options: DENY
}
}));
});
If you prefer a minimal approach without helmet-csp, you can set headers directly:
// src/middleware/frame-protection.js
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; frame-ancestors 'none'"
);
next();
});
2) Secure JWT transmission and storage on the client
Ensure JWTs are transmitted via Authorization headers and not stored in ways that encourage embedding vulnerabilities. On the client, avoid storing JWTs in localStorage when embedding is possible; prefer in-memory storage for sensitive sessions. Below is a typical FeathersJS client setup that avoids exposing tokens to embedded contexts.
// Example: FeathersJS client configuration (e.g., in a SPA)
import feathers from '@feathersjs/feathers';
import authentication from '@feathersjs/authentication-client';
import { Socket as SocketIOClient } from 'socket.io-client';
const app = feathers();
const socket = SocketIOClient('https://api.example.com');
app
.configure(authentication({
header: 'Authorization',
entity: 'user',
service: 'users',
jwtStrategy: 'jwt',
storage: window.sessionStorage // Prefer sessionStorage over localStorage
}))
.configure(socketio(socket))
.configure(feathers.authentication());
// Ensure JWT is sent via Authorization header; do not embed authenticated pages in iframes.
3) Server-side route protection and ownership checks
Even when framing is blocked, ensure each sensitive operation validates ownership and permissions. Combine standard FeathersJS hooks with explicit checks to avoid relying solely on route obscurity.
// src/hooks/require-ownership.js
const { GeneralError } = require('@feathersjs/errors');
module.exports = function requireOwnership(options = {}) {
return async context => {
const { user } = context.params;
const record = await context.service.get(context.id);
if (!record || record.userId !== user.id) {
throw new GeneralError('Forbidden', { code: 403 });
}
return context;
};
};
// Usage in a service hook
app.service('documents').hooks({
before: {
async get(context) {
const { user } = context.params;
const doc = await context.service.Model.findById(context.id);
if (!doc || doc.userId !== user.id) {
throw new GeneralError('Forbidden', { code: 403 });
}
return context;
}
},
after: { all: [] },
error: { all: [] },
});
By combining frame-ancestors set to 'none', secure JWT handling, and per-request authorization, you reduce the risk that clickjacking can exploit authenticated sessions in FeathersJS applications using JWT tokens.