MEDIUM clickjackingadonisjsjwt tokens

Clickjacking in Adonisjs with Jwt Tokens

Clickjacking in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Clickjacking is a client-side vulnerability where an attacker tricks a user into interacting with a hidden UI element through an invisible or disguised iframe. When an application uses JWT tokens for authentication but relies solely on the server to determine authorization without embedding adequate UI-level protections, the combination can expose interactive features to clickjacking. In AdonisJS, APIs typically validate JWT tokens in middleware and then render views or serve endpoints that may include forms or action URLs. If these responses are served with an overly permissive Content-Security-Policy (CSP), or without anti-clickjacking headers, an authenticated session identified by a JWT can be embedded in a malicious site. For example, an attacker could craft a page that loads your AdonisJS dashboard route inside an iframe and overlay interactive controls, leveraging the user’s valid JWT-based session to perform actions such as changing settings or submitting forms without the user’s consent. Because JWTs are often stored in cookies or local storage and sent automatically by the browser, the server may treat the request as legitimate, even though the UI context is hostile. This is especially risky for state-changing POST endpoints that do not enforce same-origin policies or require explicit UI intent verification. The risk is not in JWT validation itself, but in how responses are framed and protected in the browser, which can allow an attacker to ‘click’ through your authenticated UI invisibly.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Defending against clickjacking in an AdonisJS application using JWT tokens involves a combination of HTTP headers, CSP directives, and careful UI design. Below are specific, actionable fixes with code examples.

1. Set anti-clickjacking HTTP headers

Ensure responses include headers that prevent the page from being embedded in iframes. In AdonisJS, you can add middleware to inject these headers globally or per route.

// start/kernel.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class HttpMiddleware {
  public handle(ctx: HttpContextContract, next: () => Promise) {
    // Prevent framing by any site
    ctx.response.header('X-Frame-Options', 'DENY')
    await next()
  }
}

For more granular control, use CSP frame-ancestors instead of (or in addition to) X-Frame-Options, since X-Frame-Options is deprecated in some browsers.

// start/kernel.ts (continued)
public async handle(ctx: HttpContextContract, next: () => Promise) {
  ctx.response.header(
    'Content-Security-Policy',
    "default-src 'self'; frame-ancestors 'none'"
  )
  await next()
}

2. Protect JWT storage and transmission

JWTs should be transmitted over secure channels and stored in ways that reduce exposure to malicious UI overlays. Use httpOnly, Secure, and SameSite cookies for storing tokens rather than localStorage when possible, and enforce strict CORS policies.

// start/routes.ts
import Route from '@ioc:Adonis/Core/Route'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

Route.post('/login', async ({ request, response }: HttpContextContract) => {
  const { email, password } = request.all()
  // authenticate user ...
  const token = generateJwt({ email })

  response.cookie('auth_token', token, {
    httpOnly: true,
    secure: true, // only over HTTPS
    sameSite: 'strict',
    path: '/',
  })

  return response.json({ ok: true })
})

3. Require UI intent for sensitive actions

For critical operations (e.g., password change, fund transfer), require a user interaction that cannot be trivially automated through an iframe, such as a re-authentication step or a one-time token captured from a separate channel. Validate the origin header and consider embedding a per-request nonce in the page that must be submitted with the request.

// Example: validate Origin/Referer and a per-request CSRF-like nonce
// start/middleware/validate_ui_intent.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default async function validateUiIntent(ctx: HttpContextContract) {
  const origin = ctx.request.header('origin')
  const referer = ctx.request.header('referer')
  if (!origin?.startsWith('https://your-trusted-domain.com')) {
    ctx.response.status = 403
    throw new Error('Invalid request origin')
  }

  // Expect a nonce embedded server-side in the page and sent in headers or body
  const requestNonce = ctx.request.header('x-request-nonce')
  const sessionNonce = ctx.session.get('page_nonce')
  if (!requestNonce || requestNonce !== sessionNonce) {
    ctx.response.status = 403
    throw new Error('Missing or invalid UI intent nonce')
  }
}

4. Combine with route-specific protections

For routes that render admin panels or sensitive forms, explicitly set CSP frame-ancestors and ensure tokens are not leaked in URLs or logs.

// Example route protection
Route.get('/admin/settings', async ({ view, response }) => {
  response.header('Content-Security-Policy', "frame-ancestors 'none'")
  return view.render('admin/settings', { token: getSessionToken() })
})

Frequently Asked Questions

Does using JWT tokens in AdonisJS inherently prevent clickjacking?
No. JWT tokens handle authentication, but clickjacking is a UI framing issue. Without anti-clickjacking headers (X-Frame-Options or CSP frame-ancestors) and secure cookie settings, an authenticated JWT session can still be embedded in malicious iframes.
Should I store JWTs in localStorage or cookies in AdonisJS to prevent clickjacking?
Prefer httpOnly, Secure, SameSite=strict cookies for storing JWTs in AdonisJS. This reduces the risk of token theft via XSS and limits exposure to malicious UI overlays. Avoid localStorage for tokens if clickjacking and XSS protections are a priority.