HIGH clickjackingkoabearer tokens

Clickjacking in Koa with Bearer Tokens

Clickjacking in Koa with Bearer Tokens — how this specific combination creates or exposes the vulnerability

Clickjacking is a client-side UI redress attack where an attacker tricks a user into interacting with a hidden or disguised element inside an embedded frame. Koa, a lightweight Node.js framework, does not set frame-protection headers by default. When a Koa application uses Bearer Tokens—typically passed via the Authorization header and often reflected in UI elements or embedded widgets—clickjacking can expose token leakage or unauthorized actions. For example, an attacker can craft a page that loads the Koa app in an invisible iframe and overlays interactive controls (like "Revoke Token" or "Transfer Funds") on top of legitimate buttons. If the user is authenticated with a Bearer Token and visits the malicious page, the attacker can hijack the user’s authenticated session without their knowledge.

Bearer Tokens amplify clickjacking risks in two ways. First, if the token is used for authorization only and the UI relies on the same origin for actions (e.g., POST /transfer), an embedded frame can trigger state-changing requests because cookies and Authorization headers are sent automatically by the browser. Second, if the Koa app embeds the token in JavaScript or HTML (e.g., for initializing an SDK or client-side rendering), an attacker who can read the frame via a clickjacking vector might harvest the token through UI manipulation or social engineering. Even with CORS in place, if X-Frame-Options or Content-Security-Policy frame-ancestors are missing or too permissive, the app becomes vulnerable.

Consider a Koa route that renders a page with an authenticated widget:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx) => {
  const token = ctx.request.headers['authorization']?.split(' ')[1];
  ctx.body = `
    <div>
      <h1>Widget</h1>
      <button id="revoke">Revoke Token</button>
      <script>
        const token = "${token}"; // reflected token in page
        document.getElementById('revoke').onclick = () => {
          fetch('/api/revoke', { method: 'POST', headers: { Authorization: 'Bearer ' + token } });
        };
      </script>
    </div>
  `;
});

app.listen(3000);

If this page is loaded in an attacker-controlled site via an iframe, and the user’s browser sends the Authorization header automatically, a concealed button can trigger /api/revoke. The combination of Koa not enforcing frame restrictions and the presence of Bearer Tokens in the page creates an exploitable window. Attackers do not need to understand the internals of the token; they rely on the browser’s behavior to include credentials in cross-origin requests when the target route does not validate origin or use anti-CSRF protections.

Bearer Tokens-Specific Remediation in Koa — concrete code fixes

Remediation focuses on preventing embedding and enforcing strict frame policies, while safely handling Bearer Tokens in Koa. Below are concrete, copy-paste code examples.

1) Set frame protection headers. Use X-Frame-Options and Content-Security-Policy to restrict who can embed the app:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) =>
  ctx.set('X-Frame-Options', 'DENY');
  ctx.set(
    'Content-Security-Policy',
    "default-src 'self'; frame-ancestors 'none'"
  );
  await next();
});

app.use(async (ctx) => {
  ctx.body = '

Secure Widget

Frame embedding is blocked.

'; }); app.listen(3000);

2) Avoid reflecting Bearer Tokens in HTML/JS. Never interpolate tokens into client-side scripts. Instead, keep token handling server-side and use opaque references or session identifiers where necessary:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  ctx.set('X-Frame-Options', 'DENY');
  ctx.set(
    'Content-Security-Policy',
    "default-src 'self'; frame-ancestors 'none'"
  );
  await next();
});

app.use(async (ctx) => {
  // Do NOT expose the token to the client.
  // Use server-side sessions or secure storage instead.
  const token = ctx.request.headers['authorization']?.split(' ')[1];
  if (!token) {
    ctx.status = 401;
    ctx.body = { error: 'missing_token' };
    return;
  }
  // Perform actions server-side; do not echo token to the page.
  ctx.body = { status: 'ok', message: 'Widget rendered safely' };
});

app.listen(3000);

3) Enforce same-site and secure cookie attributes if using cookies alongside tokens. While Bearer Tokens are typically sent via headers, if your app also uses cookies for session management, set SameSite and Secure flags:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  ctx.set('X-Frame-Options', 'DENY');
  ctx.set(
    'Content-Security-Policy',
    "default-src 'self'; frame-ancestors 'none'"
  );
  // If setting cookies, protect them
  ctx.cookies.set('session', 'value', {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
  });
  await next();
});

app.use(async (ctx) => {
  ctx.body = { status: 'secure' };
});

app.listen(3000);

4) Validate origins for sensitive actions. For endpoints that perform high-risk operations (e.g., token revocation), add origin or referer checks in addition to frame policies:

const Koa = require('koa');
const app = new Koa();

const allowedOrigin = 'https://your-app.com';

app.use(async (ctx, next) =>
  if (ctx.path === '/api/revoke') {
    const origin = ctx.request.get('Origin');
    const referer = ctx.request.get('Referer');
    if (!origin || origin !== allowedOrigin) {
      ctx.status = 403;
      ctx.body = { error: 'invalid_origin' };
      return;
    }
  }
  await next();
});

app.use(async (ctx, next) => {
  ctx.set('X-Frame-Options', 'DENY');
  ctx.set(
    'Content-Security-Policy',
    "default-src 'self'; frame-ancestors 'none'"
  );
  await next();
});

app.post('/api/revoke', async (ctx) => {
  const token = ctx.request.headers['authorization']?.split(' ')[1];
  if (!token) {
    ctx.status = 401;
    ctx.body = { error: 'missing_token' };
    return;
  }
  // Proceed with revocation logic
  ctx.body = { status: 'revoked' };
});

app.listen(3000);

These steps ensure that even if a Bearer Token is present, clickjacking cannot trigger unauthorized actions because the browser will refuse to render the app in frames and the server validates origins for sensitive endpoints.

Frequently Asked Questions

Does middleBrick detect clickjacking vulnerabilities in Koa APIs?
Yes, middleBrick includes a Content-Security-Policy and frame-protection check as part of its 12 security scans. Submit your Koa endpoint to middleBrick to receive a security risk score and specific remediation guidance for clickjacking and other headers-related findings.
Can Bearer Tokens be safely used with embedded widgets if CSP is configured?
Yes, if you configure Content-Security-Policy frame-ancestors strictly and avoid reflecting Bearer Tokens in HTML or JavaScript, embedded widgets can be safer. However, prefer keeping token handling server-side and use the middleBrick CLI to verify your headers and CSP are correctly enforced.