I have a parent webpage with a child iframe:
Parent at https://sub1.some-domain.com
Child at <iframe src="https://sub2.some-domain.com"> (inside of parent)
From Parent I do POST request to the API https://api.some-domain.com
.
Below is the Response headers:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://sub1.some-domain.com
Set-Cookie:
payload-name=payload-value;
max-age=30;
domain=some-domain.com;
path=/;
secure;
samesite=none;
httponly
Afterwards,
from the Iframe I do GET request to "https://api.some-domain.com".
Expectation: Browser to include Httponly cookies payload-name=payload-value;
into the request.
Result: Httplonly cookies not included for unknown reason.
BTW, I included "withCredentials" property into JS Http request, so this couldn't be a problem.
Update
GET Request:
fetch("https://api.some-domain.com/...", {
"headers": {
"accept": "application/json",
"accept-language": "en-US,en;q=0.9,uk;q=0.8",
"cache-control": "no-cache",
"content-type": "application/json",
"pragma": "no-cache",
"sec-ch-ua": "\"Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"115\", \"Chromium\";v=\"115\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://sub2.some-domain.com/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});
GET Response headers:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://sub2.some-domain.com
Cache-Control: no-store,no-cache
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Pragma: no-cache
Update2
Locally, everything works fine, httponly cookies included.
I believe this reason is that https://api.some-domain.com
, https://sub1.some-domain.com
, https://sub2.some-domain.com
is https://localhost
.
Update3
The same is issue is when instead of iframe is pop-up window.
The latest cookie behaviours and restrictions are explained in recent updates to the RFC6265 document. There have been updates in recent years to both browsers and servers, to prevent unwelcome cross site user tracking.
SAME SITE
All of the subdomains you are using share the same parent domain of some-domain.com
. Assuming that this is not a shared domain from a hosting provider, and that you own the whole domain, this means all requests are in the same site
and should not be impacted by cookie restrictions. Section 5.2 describes the algorithm.
If the browser domain and the API domain are not in the same site, then cookies will be treated as third party and dropped aggressively, especially on iframe and Ajax requests, where the user is not informed of the origin making the API request.
CROSS ORIGIN
Requests from the first two of these domains to the API will be cross origin
and same site, since in both cases the hostname of the API and browser document is not the same. Therefore cross origin requests will trigger CORS pre-flight OPTIONS requests, which you seem to be dealing with correctly.
sub1.some-domain.com
sub2.some-domain.com
api.some-domain.com
Since you are calling the API from two web origins, make sure the CORS response headers returned for pre-flight OPTIONS requests vary by origin. I suspect that is already the case though:
Vary: Origin, Access-Control-Request-Headers
COOKIE DETAILS
If you are using authentication cookies to secure requests from the browser to APIs, then I would recommend against using SameSite=None
. This is intended only for special cases, notably third party cookies issued by authorization servers and identity providers. This prevents cookies used for logins on a top level window from being dropped. I would recommend updating your scenario to use cookie details like this:
Set-Cookie:
payload-name=payload-value;
max-age=30;
domain=api.some-domain.com;
path=/;
secure;
samesite=strict;
httponly
The strict
setting provides the strongest protection against cross site request forgery, unlike the none
setting. This also ensures that cookies are only sent to the API side of the architecture. If you are using API driven cookies in the browser, you should not need to use the cookie for web requests, if you are just getting static content. In some deployment scenarios, such as when getting web content from a content delivery network, it is more secure to avoid sending an API credential there.
FIRST PARTY IFRAMES
Assuming a same site setup, the iframe document for sub2.some-domain.com
and the main window's top level origin of sub1.some-domain.com
will be considered to have the same site for cookies
. The iframe will send an origin of https://sub2.some-domain.com
to the API and shared cookies work OK.
Use of first party iframes still has some limitations though. If you ever wanted to trigger an OAuth based login from an iframe, eg to sign in via Google, that would not work since the login cookies would be considered third party and dropped.
THIRD PARTY IFRAMES
If the same site requirement is not met, then cookies will be dropped by browsers and you won't be able to influence that. Third party iframes that use cookies therefore need to be migrated to first party iframes. This typically involves hosting the iframe in your own web domain, then routing API requests via an API domain in the same site as the web origin.
FETCH REQUESTS
Make sure that both POST request with "Set-Credentials: .." header and next GET request has "withCredentials" option. If POST request would not have it, the cookies wouldn't be set, even if you see them in DEV Tools.
Simplify your FETCH request, since request headers add complexity. Any non safe headers you send must have been first requested in a pre-flight OPTIONS request, resulting in response headers like this:
Access-Control-Allow-Headers: content-type
Simplify it something like this. Mostly the browser code only needs to opt into sending cross origin cookies. You should not need to try to manipulate the request in other ways.
fetch("https://api.some-domain.com/...", {
"headers": {
"accept": "application/json",
"content-type": "application/json"
},
"method": "GET",
"mode": "cors",
"credentials": "include"
});
My solutions have mostly used axios, since in the past I've found it to have better support for reading error response bodies. I've only needed to use basic code like this to send same site cross origin cookies to APIs:
const options = {
url,
method,
headers: {
accept: 'application/json',
},
withCredentials: true,
};
const response = await axios.request(options);
SUMMARY
I can verify that in a compliant same site setup with the right CORS permissions, you can send cookies from a web origin to a sibling API domain, and also from a sibling iframe to the same API. I have done quite a bit of work in this area, and tested your iframe scenario in all of the main browsers. Eg you can run my Online SPA, which uses API driven cookies. As a next step, see if any of this helps, and post back any results if the issue persists.