Skip to content
Innovialabs

What Is Clickjacking and How to Prevent It on Your Website

Basics3 min

In short

Clickjacking loads your site in an invisible iframe over a decoy, so users click your button while thinking they click something else. Prevent it by forbidding framing with the CSP frame-ancestors and X-Frame-Options headers.

The short answer

Clickjacking is an attack where someone loads your site in a transparent iframe on their own page and positions it exactly over a harmless-looking button such as "Claim your prize" or "Close ad".

The user sees the decoy but actually clicks the invisible button on your site. If they are logged in to your site at that moment, the action runs on their behalf — with all their permissions and cookies.

What it looks like technically

The attacker's page contains something like this:

<button style="position:absolute; top:200px; left:150px">Claim your prize</button>
<iframe src="https://example.com/settings"
        style="opacity:0; position:absolute; top:0; left:0; width:800px; height:600px; z-index:2">
</iframe>

The iframe is fully transparent (opacity:0) and sits above the button (z-index). The coordinates are tuned so that, for example, "Delete account" or "Confirm" lies right under "Claim your prize".

CSRF protection does not help here: the request comes from your own site, inside a real session, with real tokens.

Real-world impact

  • Changing settings: making a profile public, turning off security alerts.
  • Confirming actions: a transfer, a one-click purchase, a paid subscription.
  • Granting permissions: camera or microphone access, connecting a third-party app to the account.
  • Likes and follows: inflating social actions in the user's name.
  • Deleting data: "Delete" buttons with a single confirmation.

Pages where one or two clicks trigger something important are the most exposed.

How to prevent it

The core rule: the page tells the browser whether it may be framed by other sites. Two response headers do this.

HeaderValueEffect
Content-Security-Policyframe-ancestors 'none'No framing anywhere
Content-Security-Policyframe-ancestors 'self'Framing only on your own origin
Content-Security-Policyframe-ancestors 'self' https://partner.exampleYour origin plus an explicit list
X-Frame-OptionsDENY or SAMEORIGINLegacy header for older browsers

CSP frame-ancestors is the modern, flexible option. Send X-Frame-Options as well for compatibility. When both are present, modern browsers follow frame-ancestors.

Nginx example:

add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;

Next.js example in next.config.js:

module.exports = {
  async headers() {
    return [{
      source: '/(.*)',
      headers: [
        { key: 'Content-Security-Policy', value: "frame-ancestors 'self'" },
        { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
      ],
    }];
  },
};

Note: frame-ancestors does not work in a <meta> tag, only as an HTTP header.

Extra measures for critical actions:

  • Re-authentication: ask for a password or code before account deletion and transfers.
  • Session cookies with SameSite=Lax or Strict — the browser then will not send them inside a frame on another site.

How to test that a page cannot be framed

  1. Headers: open DevTools, the Network tab, select the document and check for Content-Security-Policy with frame-ancestors and for X-Frame-Options. Or run curl -I https://your-site.
  2. Hands-on test: create a local HTML file with <iframe src="https://your-site"> and open it in a browser. You should see an empty area or an error instead of the site, and the console should report a block due to frame-ancestors.
  3. Check not only the home page but also settings, checkout and admin pages — headers are sometimes set for some routes only.

Common mistakes

  • Protecting the home page but leaving the account area without headers.
  • Trying to set frame-ancestors via <meta>.
  • Relying on JavaScript "frame-busting" scripts, which can be bypassed.
  • Setting frame-ancestors * "just in case" to make some widget work.

FAQ

The risk is lower, but add the header anyway: it is one line of configuration, and the site may later gain forms, payments or a login.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.