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:
// 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.