Question

Enforcing a web page to be iframed?

I have an HTML page that is designed to be iframed into a different website.

With a CSP frame-ancestors directive I can restrict in which pages it is allowed to be iframed. This disallows iframing into the wrong site which is the intent of this security policy.

In my scenario I also want to block loading this page stand alone because it does not make sense and will confuse people trying to interact with it. It only should be used in the iframed context.

As far as I can tell CSP does not have an option to block a page from being run standalone. It works as a whitelist so I can only allow more iframes and not deny something.

So far I have only found client-side solutions (like this which uses JavaScript) to detect this in the browser and then do something. Although this is a viable solution I prefer already blocking this at the server end.

I have had a look at the request headers and found that only on the first request the "Referer:" seems to be 'usable' but not reliable.

So my question is if it is possible to detect/block this serverside or is there a way to make the browser block this (similar to a CSP)? Or is the JavaScript route really the only current way to handle this?

 2  37  2
1 Jan 1970

Solution

 0

If

window.parent === window

then your page is not inside an iframe. Yet, this is only reliable if you have the same origin. Since your scenario is all about cases when the origin is not the same, you will need a more neat solution. A way to solve this is to make sure that your page will only be displayed if it receives a message.

Steps to implement this:

1. Make sure that the page does not load your actual structure upon GET

Make sure that your page is having a proper head, with link and script and meta tags and an empty body.

2. Make sure your script embedded into the iframe has a postMessage handler

Read about postMessage here. Example from that page:

window.addEventListener(
  "message",
  (event) => {
    if (event.origin !== "http://example.org:8080") return;

    // …
  },
  false,
);

Make sure that inside the data you pass something that validates the page being displayed, like a token that your server understands.

3. Send a request from the page upon receiving a token to the server

So, you receive some data, let's make sure that you send it to the server and, if valid, then receive the contents that you wanted to display and append them to body as innerHTML. A simplistic solution would be at your server to generate a one-time alphanumeric token (maybe a random text) that is linked to the host of the page where your iframe is to be embedded and once your server receives this request, check whether the token exists and it is linked to the exact host that has sent you the host, respond to the request with valid content (if the token was valid) or an error message (if the token was invalid) and of course, remove the token after validating, but before responding.

4. Script for the parent page to embed

Implement a Javascript script that

  • will be embedded into the pages that embed your page into an iframe
  • checks whether the iframe exists and points to the location you expect
  • sends a request to the server, requesting a token
  • triggers a message event, targeting your iframe page and passing the token (call postMessage and pass valid data, including the token)
2024-07-24
Lajos Arpad

Solution

 0

I have found a way to do this that works with only modern browsers (good enough for me) and works in my specific context.

Why and how this works for my context:

  • my iframe gets specific values in the first request from the page in which it is iframed ( a bunch of query parameters in the url ).
  • my iframed application then stores this in a session (I'm doing spring-boot using kotlin) and the session ID is kept as a cookie in the browser.
  • on a next interaction in this iframe the data from the session is seen and thus allows the next steps.

The problem is that if AFTER you have done this (i.e. there is a cookie with an active session) and then open the iframed url in a separate window utside of the iframed situation; it will work (which I do not want).

While trying things out I noticed a message in the browser console

  • Chrome: Third-party cookie will be blocked in future Chrome versions as part of Privacy Sandbox..
  • Edge: Third-party cookie will be blocked in future Microsoft Edge versions as part of unpartitioned third-party cookie deprecation.

So I did some digging about this partitioning and it turns out that if a cookie is partitioned then it will have a separate scope between inside and outside the iframe.

So if the page in the iframe received the parameters that make it valid, then the same page opened outside the iframe will be in a different session! A different session which is missing the required attributes and thus I can cleanly show a serverside error.

A skilled user can easily circumvent this by changing their cookies. For the usecase I have this is enough because it makes this happening by accident impossible.

After some further digging I found that enabling this is quite easy (once you know how to do it):

Based upon this https://github.com/spring-projects/spring-framework/issues/31454#issuecomment-1905442337 I now have this in my (kotlin) code and I have what I wanted.

@Bean
fun forceAllCookiesToBePartitioned(): TomcatContextCustomizer {
    val cookieProcessor = Rfc6265CookieProcessor()
    cookieProcessor.partitioned = true

    return TomcatContextCustomizer { context: Context ->
        context.usePartitioned = true
        context.cookieProcessor = cookieProcessor
    }
}
2024-07-24
Niels Basjes