Clickjacking in Feathersjs with Basic Auth
Clickjacking in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side attack that tricks a user into interacting with a hidden or disguised UI element inside an embedded frame. When FeathersJS services are used with Basic Authentication and are not explicitly protected against framing, an authenticated session can be abused by an attacker’s page to load the application in an invisible iframe and drive interactions via CSRF-like UI events.
With Basic Auth, credentials are sent with each request via the Authorization header. If a FeathersJS app relies solely on cookies for session linkage (e.g., session cookies set after Basic Auth validation) and does not enforce anti-CSRF measures or X-Frame-Options, an attacker can embed the authenticated app in an iframe. Even though Basic Auth headers are not sent to the embedded frame if the origin does not match and the server does not allow cross-origin credentials, a vulnerable FeathersJS endpoint that accepts state-changing methods over GET or does not validate origin/referrer can be invoked via JavaScript running in the attacker’s page. This becomes feasible when CORS is permissive or when cookies/sessions are used for authorization context rather than strict token validation, allowing the embedded app to perform actions on behalf of the authenticated user.
FeathersJS by default does not set anti-clickjacking headers. If Basic Auth is used without additional protections — such as strict CORS, anti-CSRF tokens, or frame-deny headers — an authenticated session can be at risk. The framework’s hook architecture and REST/socket interfaces can inadvertently expose state-changing operations via GET requests or weakly validated origins, enabling an attacker to craft a malicious page that issues unwanted requests when the victim’s browser executes JavaScript within the embedded context.
Basic Auth-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on preventing the app from being embedded in frames and hardening Basic Auth usage in FeathersJS. Apply security headers, enforce strict CORS, and avoid relying on GET for state changes. The following examples assume a standard FeathersJS server setup with @feathersjs/express and @feathersjs/authentication.
1. Set anti-clickjacking headers
Use helmet to send X-Frame-Options and Content-Security-Policy frame-ancestors.
const feathers = require('@feathersjs/express');
const helmet = require('helmet');
const app = feathers();
// Protect against embedding in iframes
app.configure(helmet({
frameguard: { action: 'deny' } // X-Frame-Options: DENY
}));
// Alternatively, set CSP frame-ancestors explicitly
app.use(helmet.contentSecurityPolicy({
directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(),
frameAncestors: ["'none'"]
}
}));
2. Strict CORS configuration
Ensure CORS does not allow credentials from untrusted origins. With Basic Auth, avoid wildcard origins and credentialed requests from unknown domains.
const cors = require('@koa/cors'); // or @feathersjs/express if using Express
const app = feathers();
app.configure(cors({
origin: 'https://your-trusted-domain.com',
credentials: true
}));
3. Prefer token-based auth over Basic Auth where possible
Basic Auth sends credentials on every request; pair it with strong anti-CSRF practices. If you must use Basic Auth, ensure endpoints validate Origin/Referer and use CSRF tokens for state-changing methods.
// Example: Basic Auth hook with origin validation
const { iff, isProvider } = require('feathers-hooks-common');
const basicAuth = require('feathers-authentication-hooks').basicAuth;
app.service('todos').hooks({
before: {
create: [
basicAuth(),
iff(isProvider('rest'), (context) => {
const allowedOrigin = 'https://your-trusted-domain.com';
const requestOrigin = context.params.headers.origin || context.params.headers.referer;
if (requestOrigin !== allowedOrigin) {
throw new Error('Invalid origin');
}
return context;
})
],
// Apply similar checks for patch, update, remove
}
});
4. Avoid GET for mutations; use hooks to enforce referrer/origin checks
Ensure state-changing operations are only available via POST/PUT/PATCH/DELETE and validate the HTTP Referer or Origin header on the server side.
// Example hook to validate referrer for REST requests
const { iff, isProvider, preventChanges } = require('feathers-hooks-common');
app.service('comments').hooks({
before: {
create: [
iff(isProvider('rest'), (context) => {
const referer = context.params.headers.referer;
const origin = context.params.headers.origin;
if (!referer || (!referer.includes('your-trusted-domain.com') && !origin.includes('your-trusted-domain.com'))) {
throw new Error('Invalid referrer/origin');
}
return context;
})
]
}
});
5. Use anti-CSRF tokens for browser clients
If your app serves HTML/JS to browsers, include CSRF protection in forms and AJAX requests. Middleware like csurf (for Express) can be integrated before Feathers routes/hooks handle requests.
const csurf = require('csurf');
const cookieParser = require('cookie-parser');
const express = require('express');
const app = express();
app.use(cookieParser());
app.use(csurf({ cookie: true }));
// Ensure Feathers receives the CSRF token via headers or body for API calls
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});