Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogCoursesPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Courses
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
SecurityJune 25, 20264 min read

Preventing Clickjacking with Modern Frame-Ancestors Policies

Clickjacking prevention is essential for modern web apps. Learn how to implement Content Security Policy and X-Frame-Options to stop UI redressing attacks.

Web SecurityContent Security PolicyClickjackingHTTP HeadersUI RedressingSecurityWebBackend

During an on-call rotation last year, I spent an entire afternoon debugging why our support dashboard was suddenly triggering weird UI behavior for our users. It turned out to be a classic case of Clickjacking, where a malicious site had loaded our admin panel inside an invisible iframe, tricking our staff into performing actions they didn't intend to. We fixed it quickly, but it was a sobering reminder of how easy it is to overlook UI redressing vulnerabilities.

What is Clickjacking?

At its core, Clickjacking is a visual deception. An attacker loads your legitimate website in an iframe on a site they control. They then overlay that iframe with transparent elements, making the user think they’re clicking a "Win a Free iPad" button when they’re actually clicking "Delete Account" on your site.

If you aren't explicitly telling browsers who is allowed to frame your content, they assume the answer is "everyone." That’s a dangerous default in today's landscape. Just like we have to be careful with Preventing DOM-based XSS or Preventing Improper CORS Policy Configuration, framing controls are a non-negotiable part of your security headers.

The Old Guard: X-Frame-Options

Before modern policies, we relied on the X-Frame-Options (XFO) header. It’s simple, widely supported, and essentially acts as a binary switch for your site's framing capabilities.

You generally have two options:

  • DENY: No one can frame your site.
  • SAMEORIGIN: Only pages on the same origin as the framed page can frame it.

We used SAMEORIGIN for about two years across our main applications. It works, but it’s inflexible. If you ever need to allow a specific partner site or a subdomain to frame your content, X-Frame-Options falls apart. It’s a blunt instrument in a world that often requires surgical precision.

The Modern Standard: Content Security Policy

This is where the frame-ancestors directive in your Content Security Policy (CSP) comes in. It’s the successor to XFO and offers far more granular control. Instead of a simple "yes" or "no," you can define exactly which domains are authorized to embed your application.

Implementing CSP frame-ancestors

To implement this, you’ll send a Content-Security-Policy header from your server. Here is how I typically configure it for a standard production app:

HTTP
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;

In this example, 'self' allows your own origin to frame the page, and https://trusted-partner.com allows that specific domain to embed your app. If you want to block all framing entirely, you would use:

HTTP
Content-Security-Policy: frame-ancestors 'none';

Why You Should Use Both

You might be wondering: "If CSP is the modern standard, can I drop X-Frame-Options?"

Technically, yes, if you only support modern browsers. However, I still recommend sending both headers. It’s a "defense in depth" strategy. If a user happens to be on a legacy browser that doesn't understand CSP, it will ignore the frame-ancestors directive but still respect the X-Frame-Options header.

The configuration looks like this in a typical Express.js middleware:

JAVASCRIPT
app.use((req, res, next) => {
  res.setHeader(CE9178">'X-Frame-Options', CE9178">'SAMEORIGIN');
  res.setHeader(CE9178">'Content-Security-Policy', "frame-ancestors CE9178">'self' https://trusted-partner.com");
  next();
});

Lessons Learned and Trade-offs

When we first rolled this out, we broke our own internal tool that required cross-domain framing for a legacy dashboard. We initially thought a blanket DENY was the right move, but we failed to account for our internal micro-frontends.

We had to pivot to a whitelist approach using frame-ancestors. It took about two days to map out every valid origin that actually needed to frame our content. It was tedious, but it was worth the effort to stop the potential for UI redressing attacks.

If I were starting this from scratch today, I’d start with frame-ancestors 'none' in my development environment. It forces you to be explicit about your requirements from day one. If something breaks, you know exactly why—you haven't authorized that origin yet.

Frequently Asked Questions

Does this protect against all forms of UI redressing? No. While frame-ancestors stops framing attacks, it doesn't protect against other forms of UI redressing that don't involve iframes, such as using CSS to manipulate the layout of your page if you allow user-generated content. Always sanitize your inputs.

What happens if I set both headers to different values? Browsers are smart enough to handle this, but the behavior can vary. Generally, if the browser supports CSP, it will prioritize frame-ancestors and ignore X-Frame-Options. Always keep them consistent to avoid confusing your team during an incident.

Should I use 'unsafe-inline' in my CSP? That's a different discussion regarding XSS, but keep in mind that your CSP is a single header. Don't mix up your frame-ancestors policy with your script-src policies. Keep your headers clean and modular.

Securing your frames is a low-effort, high-impact task. It takes about 20 minutes to implement and protects your users from one of the most insidious types of browser-based attacks. Don't wait for a production incident to add these headers to your stack.

Back to Blog

Similar Posts

SecurityJune 24, 20264 min read

Rate limiting API security: A Practical Guide for Node.js and Laravel

Master rate limiting for API security. Learn to defend your Node.js and Laravel endpoints against brute-force attacks and resource exhaustion in production.

Read more
SecurityJune 25, 20264 min read

Parameter pollution: Securing Express and Laravel Request Parsing

Parameter pollution can lead to serious logic flaws in your web apps. Learn how to secure your Express and Laravel request parsing against manipulation.

Read more
SecurityJune 25, 20264 min read

Preventing DOM-based XSS: A Guide for Modern JavaScript Apps

Learn how to stop DOM-based XSS by securing your client-side sinks and sources. Master practical input sanitization and secure coding techniques today.

Read more