cookies

Why do browsers treat requests in an iframe as cross-site requests as it relates to cookies, but same-site for the request itself?


If a run an application containing an iframe on 127.0.0.1:5500

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<script>

  document.addEventListener("DOMContentLoaded", () => {
    // This here is to demonstrate that the a request to localhost:3000 will 
    // give a CORS error 
    fetch("http://localhost:3000/req1").then((res) => {
      console.log(res);
    });
  })
</script>
<body>
    <h1>I am the outer application</h1>
    <iframe src="http://localhost:3000" width="100%" height="500px"></iframe>
</body>
</html>

And the application at localhost:3000 makes requests to /req1, /req2 /req3, and these will return some data, as well as setting a cookie, and no cross origin headers.

eg.

//req1
export async function GET() {
  const x =  NextResponse.json({ error: 'I am req 1 data' }, { status: 200 })
    
  x.cookies.set("my-cookie-1", "hello world");

  return x;

}
//req2 
export async function GET() {
  const x =  NextResponse.json({ error: 'I am req 2 data' }, { status: 200 })
    
  x.cookies.set("my-cookie-2", "hello world", {
    "sameSite":"none", 
    "secure": true,
  });

  return x;

}

What I observe is that the iframe is able to make the requests and the data and cookies are retrieved fine. They're considered same-site requests in this sense.

However, unless we add secure: true and sameSite: "none" the cookies are not attached on subsequent requests.

In Chrome's devtools we see the message:

The cookie didn't specifiy a "SameSite" attribute when it was sotred and was defaulted to "SameSite=Lax," and was blocked because the request was made from a different site and was not initiated by a top-level navigation. The cookie had to be set with "SameSite=None" to enable cross-site usage.

Why does the cookie treat this as a cross-site request, when the request itself does not?

I have a reproduction for this here:

https://github.com/dwjohnston/cookies-and-iframes


Solution

  • // Why does the cookie treat this as a cross-site request, when the request itself does not?

    These are fundamentally two different security mechanisms (Cookies same-site vs CORS same-origin) with related but different security motivations.

    Cookies - Same-Site

    In your reproduction, cookies determine a cross-site status from the context of the top level document vs the iframe document. Noting that 127.0.0.1:5500 is not considered to be the same-site as localhost:3000

    The spec ensures that only Same-Site: None secure cookies are set/attached in cross-site contexts which includes iframes.

    I assume the motivation for browsers applying it to iFrame subresources like this is some form of defence in depth (In addition to X-Frame-Options/CSP). For example, if a malicious actor was able to craft an XSS exploit via URL, the user could embed this url into an iframe as one delivery mechanism.

    CORS - Same-Origin

    In the context of CORS, same-origin status is determined from the current document origin & the origin of the resource that is being requested.

    In comparison to cookies, there is no special distinction around the top level document visible URL (which is why your sub-iframe requests operate as expected).


    What I observe is that the iframe is able to make the requests and the data and cookies are retrieved fine. They're considered same-site requests in this sense.

    This is because the server is not responsible for determining if something is considered same-site or not. The browser has the job of determining if the cookies should be set/not set based off the context of how the request was made.

    Note that this is the same as CORS, the browser is responsible for actually enforcing CORS policies.


    To summarise, There is no CORS in play for the sub iframe (as the API request is on the same origin as the iframe document). But there is a cross-site cookie usage (As the top level document uri is a different site to the iframe uri).

    Side note, if you want to limit the scope that a Same-Site: None cookie has, it's worth looking into partitioned cookies. They limit the scope to the top level site, which would have been a much saner default.