Welcome to a tutorial on how to set a CORS cookie in PHP. Need to set a cookie in a cross-origin call? Annoyingly got rejected multiple times? Yep, that is security, and it took me hours to figure things out too. So here it is, a quick sharing of my working example – Read on!
ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.
TLDR – QUICK SLIDES
Fullscreen Mode – Click Here
TABLE OF CONTENTS
DOWNLOAD & NOTES
Firstly, here is the download link to the example code as promised.
QUICK NOTES
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.
EXAMPLE CODE DOWNLOAD
Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
TEST SERVER SETUP
For you guys who don’t have multiple domains to do testing, no sweat. Here is how I set up my localhost to emulate multiple domains.
OS HOSTS FILE
127.0.0.1 site-a.com
127.0.0.1 site-b.com
::1 site-a.com
::1 site-b.com
First, open the hosts
file and add the following entries. For the uninitiated, this is just a manual DNS override – Point site-a.com
and site-b.com
to localhost.
P.S. Mac/Linux users, yours should be in etc/hosts
, but it may differ for different distros.
VIRTUAL HOSTS
<VirtualHost *:80 *:443>
DocumentRoot "D:/http/"
ServerName localhost
</VirtualHost>
<VirtualHost *:80 *:443>
DocumentRoot "D:/http/site-a/"
ServerName site-a.com
</VirtualHost>
<VirtualHost *:80 *:443>
DocumentRoot "D:/http/site-b/"
ServerName site-b.com
</VirtualHost>
Next, create the project folders and map the virtual hosts accordingly. That is, https://site-a.com
will point to D:/http/site-a/
and https://site-b.com
will point to D:/http/site-b/
.
P.S. This is for Apache. IIS and NGINX users – Do your own research.
SSL CERT
Lastly, using https://
is a vital part to make CORS cookies work. If you have not already done so, self-sign and set your own SSL certs. Not going to walk through that here, it is quite a hassle, but there are plenty of good guides all over the Internet – Do your own research again.
PHP CORS COOKIE
All right, let us now get into the step-by-step example of setting cookies in a CORS AJAX fetch call.
STEP 1) ACCESS SITE A & SITE B WITH HTTPS
- Open your browser and access
https://site-a.com/1-fetch.html
. Yes,HTTPS
. - Your browser or anti-virus is going to complain “self-signed SSL is unsafe”. Sure thing Sherlock, I signed that cert myself. Go ahead and click on “continue”.
- Open a new tab and access
https://site-b.com/3-show.html
. Do the same and access the “unsafe site”.
STEP 2) FETCH CALL FROM SITE A
<!-- (A) DEMO BUTTON -->
<input type="button" value="CORS Cookie" onclick="demo()">
<script>
function demo () {
// (B) CORS FETCH TO SITE B
fetch("https://site-b.com/2-demo.php", {
mode : "cors",
credentials : "include"
})
// (C) FOR DEBUGGING... CONSOLE LOG ALL RESPONSES
.then(res => {
console.log(res);
return res.text();
})
.then(res => console.log(res))
.catch(err => console.log(err));
}
</script>
On site-a.com
, we will do a fetch()
call to site-b.com
. Take note of the settings:
mode : "cors"
Updated browsers should automatically detect and set this… But let’s just put this here for backward compatibility.credentials : "include"
That is, include the use of cookies.- Lastly, the use of
https://
– Very important, usinghttp://
will not allow cross-domain cookies to be set.
STEP 3) SITE B CALL HANDLER
<?php
// (A) GET REQUEST ORIGIN
if (array_key_exists("HTTP_ORIGIN", $_SERVER)) {
$origin = $_SERVER["HTTP_ORIGIN"];
} else if (array_key_exists("HTTP_REFERER", $_SERVER)) {
$origin = $_SERVER["HTTP_REFERER"];
} else {
$origin = $_SERVER["REMOTE_ADDR"];
}
// (B) CHECK ALLOWED DOMAINS
if (!in_array(
parse_url($origin, PHP_URL_HOST),
["site-a.com", "site-b.com"]
)) {
http_response_code(403);
exit("$origin not allowed");
}
// (C) PROCEED
header("Access-Control-Allow-Origin: $origin");
header("Access-Control-Allow-Credentials: true");
setcookie("It", "Works", [
"expires" => time()+3600,
"path" => "/",
"domain" => ".site-b.com",
"secure" => true,
"samesite" => "None"
]);
echo "OK";
On site-b.com
:
Access-Control-Allow-Origin
cannot be set as the wildcard*
. Thus, the extra bits of code to restrict cross-domain calls from specific domains only.Access-Control-Allow-Credentials
must be set totrue
.- For the cookie itself:
- Take note of the
domain
– We are making a call fromsite-a.com
, but this cookie is meant forsite-b.com
. - The
samesite
rule is set tonone
, sosecure
must be set totrue
.
- Take note of the
STEP 4) VERIFICATION
DEVELOPER’S CONSOLE
Go ahead and click on the test button. To verify the exchange in Chromium-based browsers, open the developer’s console > Network > 2-demo.php > Cookies.
SITE B ITSELF
<div id="demo"></div>
<script>
document.getElementById("demo").innerHTML = document.cookie;
</script>
Reload https://site-b.com/3-show.html
, the cookie It=Works
should also show up in the <div>
.
EXTRA BITS & LINKS
That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.
HOW ABOUT XMLHTTPREQUEST?
Yes, it works too.
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
COMPATIBILITY CHECKS
- Arrow Functions – CanIUse
- Fetch – CanIUse
Works on all modern “Grade A” browsers.
LINKS & REFERENCES
INFOGRAPHIC CHEAT SHEET

THE END
Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!