MEDIUM clickjackingadonisjsmutual tls

Clickjacking in Adonisjs with Mutual Tls

Clickjacking in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability

Clickjacking is a client-side UI redressing attack where an attacker tricks a user into interacting with a hidden or disguised element inside an embedded frame. AdonisJS applications that render HTML are susceptible if they do not enforce frame-embedding restrictions, regardless of transport security. When Mutual TLS is used, the server validates the client certificate, but this transport-layer assurance does not affect the browser’s behavior regarding framing. As a result, a page served over mTLS can still be loaded inside an iframe or frame on a malicious site if anti-clickjacking headers are absent.

Mutual TLS changes the trust boundary for authentication—both client and server present certificates—but it does not change how the browser parses and renders responses. If an AdonisJS route sets a low Content-Security-Policy frame-ancestors directive or omits X-Frame-Options, an attacker can embed the authenticated mTLS-protected page and overlay interactive elements (e.g., hidden buttons or forms) to capture unintended actions. An mTLS endpoint that returns sensitive UI—such as a confirmation page or a settings form—becomes a target for clickjacking precisely because developers may assume mTLS alone prevents unauthorized interactions.

Consider an AdonisJS application using Mutual TLS for admin endpoints. A route like /admin/confirm-action validates the client certificate and returns an HTML page with a prominent button. Without X-Frame-Options or a strict Content-Security-Policy, an attacker’s page can frame this route. Even though the mTLS handshake ensures the client is authenticated, the framed page can be visually disguised (e.g., styled to look inert) while the attacker listens for clicks via JavaScript overlays or transparent layers. The user, already possessing a valid certificate, unknowingly triggers state-changing requests inside the embedded context.

To detect this with middleBrick, you can submit the mTLS-enabled endpoint (e.g., https://api.example.com/admin/confirm-action) for a scan. The tool checks for missing or weak framing defenses and maps findings to OWASP API Top 10 and related browser security mechanisms. Note that middleBrick performs black-box testing and does not assume internal architecture, so it evaluates the live response headers regardless of whether mTLS was used during the scan.

Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes

Remediation centers on HTTP headers that prevent framing, independent of the transport-layer client certificate validation. For AdonisJS, apply headers globally or per route to ensure framed content is never rendered in an untrusted context.

  • Set X-Frame-Options to DENY or SAMEORIGIN to instruct browsers not to allow framing.
  • Use Content-Security-Policy with a strict frame-ancestors directive to whitelist trusted parents or deny all ('none').
  • Combine both headers for defense-in-depth, ensuring older and newer browsers are covered.

Below are concrete AdonisJS examples that demonstrate how to enforce these headers while using Mutual TLS.

Global middleware approach

Register a middleware that adds security headers to every response. This is ideal when all routes should reject framing.

// start/kernel.ts
import { middleware } from '@adonisjs/core/http'

export const globalMiddleware = [
  middleware.header({
    'X-Frame-Options': 'DENY',
  }),
  middleware.header({
    'Content-Security-Policy': "frame-ancestors 'none'",
  }),
]

Route-specific approach

Apply headers only to sensitive routes, such as admin or confirmation endpoints that return UI after mTLS authentication.

// start/routes.ts
import Route from '@ioc:Adonis/Core/Route'

Route.get('/admin/confirm-action', async ({ response }) => {
  response.header('X-Frame-Options', 'DENY')
  response.header('Content-Security-Policy', "frame-ancestors 'none'")
  return view.render('admin/confirm')
})

Conditional framing based on origin

If you must allow embedding from specific origins (rare for high-security actions), use a dynamic CSP value. This example allows framing only from the same origin.

// start/routes.ts
import Route from '@adonisjs/core/http'

Route.get('/settings', async ({ request, response }) => {
  const csp = `frame-ancestors 'self' ${request.headers().origin || "'none'"}`
  response.header('X-Frame-Options', 'SAMEORIGIN')
  response.header('Content-Security-Policy', csp)
  return view.render('settings')
})

These header-based controls work alongside Mutual TLS, which handles peer authentication at the TLS layer. mTLS ensures the identity of the client, but you must still protect the UI surface against clickjacking through framing restrictions.

Frequently Asked Questions

Does Mutual TLS prevent clickjacking in AdonisJS?
No. Mutual TLS authenticates the client at the transport layer but does not stop the browser from rendering the page inside an iframe. You must still use X-Frame-Options and Content-Security-Policy frame-ancestors to prevent clickjacking.
Can middleBrick detect clickjacking on mTLS endpoints?
Yes. middleBrick scans the live response headers regardless of how the endpoint is secured. Submit your mTLS-protected URL to evaluate framing defenses and receive remediation guidance mapped to frameworks like OWASP API Top 10.