DEV Community

Amr Abumady
Amr Abumady

Posted on Originally published at clientn.com

Clickjacking Still Targets Approval Buttons: A Checklist for Login Teams

Somewhere on your site there is a button that says Approve. It might confirm a sign-in on a new device, let a third-party app read an account, or authorize a payment. Clickjacking makes the person press it for a reason they never meant to.

The defenses have existed for over a decade. What has changed is the target. The approval step after a login is often the most consequential click on a site, and it is the one teams most often leave unprotected.

I'm building ClientN, so I have a stake in approval flows. The checklist below applies whatever you use.

Send the frame policy as a header

In October 2013 the IETF published RFC 7034, an informational document about the X-Frame-Options header. It tells the browser whether other sites may frame your page. The attacker's page cannot change the header your server sends.

Its successor is the frame-ancestors directive from CSP. For approval pages, a narrow rule is usually right:

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
Enter fullscreen mode Exit fullscreen mode

Keep X-Frame-Options alongside it for older browsers. Two details catch teams out:

  • The CSP3 specification says frame-ancestors is not supported in a <meta> element, so the policy must arrive as an HTTP header.
  • Check every response that shows the approval, not only the main route. A retry page or a self-reloading confirmation can be a separate response with its own headers.

Frame-busting JavaScript is at best a second layer. It runs inside the framed page, and the framing page controls that context.

The variant headers do not stop

In January 2025, BankInfoSecurity reported on DoubleClickjacking. According to that report, the technique uses two windows instead of a frame. A decoy window closes on the first click and reveals a sensitive page underneath, and the second click lands on that page. The researcher, Paulos Yibelo, showed proof-of-concept account takeovers on Salesforce, Slack and Shopify. The report says popular defenses such as Content Security Policy and SameSite cookies fail against it.

So a header does not cover window switching. Two things are worth testing:

  • A second, deliberate step that a decoy page cannot complete, such as a passkey or device PIN prompt.
  • A short delay before a high-risk button becomes active.

These are design choices to test, not guarantees.

Checklist

  1. Inventory every page where one click approves something: device sign-ins, consent screens, payment confirmations, recovery-code reveals, account deletion, and login-method changes.
  2. Send the policy on those responses, then test it. Load the page in a frame on another origin and confirm the browser refuses to render it. From a terminal: curl -sI https://your.site/approve | grep -iE 'frame-ancestors|x-frame-options'
  3. Show what is approved: the requesting app or device, and the access it asks for.
  4. Require a second step for anything that grants access or changes the account.
  5. Log every approval: the app or device, the time, whether a second step completed, and which page the request came from.

Where sign-in fits

An approval is tied to an account, and the account is usually tied to a stored email address. If the approval happened on the person's own device, with a key that stays there, a site could confirm that the same person approved a request without storing who that person is. That is a design direction, not a claim that every approval works this way today.

ClientN explores that split. The security page describes how approvals work, and the docs show how to start.


Originally published on the ClientN blog.

Top comments (0)