{"id":2123,"date":"2022-11-30T00:00:36","date_gmt":"2022-11-30T00:00:36","guid":{"rendered":"https:\/\/code-boxx.com\/?p=2123"},"modified":"2023-11-13T02:27:06","modified_gmt":"2023-11-13T02:27:06","slug":"cors-cookie-php","status":"publish","type":"post","link":"https:\/\/code-boxx.com\/cors-cookie-php\/","title":{"rendered":"Set CORS Cookie In PHP (A Step-By-Step Guide)"},"content":{"rendered":"<p>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 &#8211; Read on!<\/p>\n<p>&nbsp;<\/p>\n<div id=\"ezoic-pub-ad-placeholder-101\"><\/div>\n<p>&nbsp;<\/p>\n<h2 class=\"in-head-d\">TABLE OF CONTENTS<\/h2>\n<div class=\"toc\">\n<div><a href=\"#sec-download\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2019\/08\/ico-download.svg\" width=\"36\" height=\"36\" \/>Download &amp; Notes<\/a><\/div>\n<div><a href=\"#sec-setup\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2019\/08\/ico-gear.svg\" width=\"36\" height=\"36\" \/>Server Setup<\/a><\/div>\n<div><a href=\"#sec-cors\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2019\/05\/ico-cookie.svg\" width=\"36\" height=\"36\" \/>PHP CORS Cookie<\/a><\/div>\n<div><a href=\"#sec-extra\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2019\/08\/ico-plus-square.svg\" width=\"36\" height=\"36\" \/>Extras<\/a><\/div>\n<div><a href=\"#sec-end\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2019\/08\/ico-thumbs-up.svg\" width=\"36\" height=\"36\" \/>The End<\/a><\/div>\n<\/div>\n<p>&nbsp;<\/p>\n<div id=\"sec-download\" class=\"sec-head\" style=\"background: #ffe8de;\">\n<h2 class=\"in-head-d\">DOWNLOAD &amp; NOTES<\/h2>\n<p>Here is the download link to the example code, so you don&#8217;t have to copy-paste everything.<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">EXAMPLE CODE DOWNLOAD<\/span><\/h3>\n<p><a href=\"https:\/\/gist.github.com\/code-boxx\/487cd4f73f8a69bac9a26a5a1091602e\" target=\"_blank\" rel=\"noopener noreferrer\">Source code on GitHub Gist<\/a><\/p>\n<p>Just click on &#8220;download zip&#8221; or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.<\/p>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">SORRY FOR THE ADS...<\/span><\/h3>\r\n<p>But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a \"paid scripts\" business, and I don't \"block people with Adblock\". Every little bit of support helps.<\/p>\r\n\r\n<p><a class=\"ast-button\" href=\"https:\/\/www.paypal.com\/paypalme\/wstoh\/5\" target=\"_blank\" rel=\"nofollow noopener\">Buy Me A Coffee<\/a> <a class=\"ast-button\" href=\"https:\/\/payhip.com\/codeboxx\" target=\"_blank\" rel=\"nofollow noopener\">Code Boxx eBooks<\/a><\/p>\n<p>&nbsp;<\/p>\n<div id=\"sec-setup\" class=\"sec-head\" style=\"background: #e0f3ff;\">\n<h2 class=\"in-head-d\">TEST SERVER SETUP<\/h2>\n<p>For you guys who don&#8217;t have multiple domains to do testing, no sweat. Here is how I set up my localhost to emulate multiple domains.<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">OS HOSTS FILE<\/span><\/h3>\n<div class=\"ref-head\">C:\\Windows\\System32\\drivers\\etc\\hosts<\/div>\n<pre><code>127.0.0.1 site-a.com\r\n127.0.0.1 site-b.com\r\n::1 site-a.com\r\n::1 site-b.com<\/code><\/pre>\n<p>First, open the <code>hosts<\/code> file and add the following entries. For the uninitiated, this is just a manual DNS override &#8211; Point <code>site-a.com<\/code> and <code>site-b.com<\/code> to localhost.<\/p>\n<p>P.S. Mac\/Linux users, yours should be in <code>etc\/hosts<\/code>, but it may differ for different distros.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"ezoic-pub-ad-placeholder-102\"><\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">SSL CERT<\/span><\/h3>\n<p>Next, using <code>https:\/\/<\/code> is a vital part to make CORS cookies work. If you don&#8217;t already have your own SSL certs, self-sign and create your own. Not going to walk through that here, but there are plenty of good guides all over the Internet &#8211; Do some of your own research.<\/p>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">VIRTUAL HOSTS<\/span><\/h3>\n<div class=\"ref-head\">httpd-vhosts.conf<\/div>\n<pre><code>&lt;VirtualHost *:443&gt;\r\n  DocumentRoot \"D:\/http\/site-a\/\"\r\n  ServerName site-a.com\r\n  SSLEngine On\r\n  SSLCertificateFile \"C:\/xampp\/apache\/conf\/ssl.crt\/server.crt\"\r\n  SSLCertificateKeyFile \"C:\/xampp\/apache\/conf\/ssl.key\/server.key\"\r\n&lt;\/VirtualHost&gt;\r\n&lt;VirtualHost *:443&gt;\r\n  DocumentRoot \"D:\/http\/site-b\/\"\r\n  ServerName site-b.com\r\n  SSLEngine On\r\n  SSLCertificateFile \"C:\/xampp\/apache\/conf\/ssl.crt\/server.crt\"\r\n  SSLCertificateKeyFile \"C:\/xampp\/apache\/conf\/ssl.key\/server.key\"\r\n&lt;\/VirtualHost&gt;<\/code><\/pre>\n<p>Lastly, create the project folders and map the virtual hosts accordingly. That is, <code>https:\/\/site-a.com<\/code> will point to <code>D:\/http\/site-a\/<\/code> and <code>https:\/\/site-b.com<\/code> will point to <code>D:\/http\/site-b\/<\/code>.<\/p>\n<p>P.S. This is for Apache. IIS and NGINX users &#8211; Do your own research.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"ezoic-pub-ad-placeholder-103\"><\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">ACCESS SITE A &amp; SITE B WITH HTTPS<\/span><\/h3>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2022\/11\/php-cors-cookie-a-20230511.webp\" alt=\"\" width=\"605\" height=\"410\" \/><\/p>\n<ul>\n<li>Open your browser and access <code>https:\/\/site-a.com\/<\/code>. Yes, <code>HTTPS<\/code>.<\/li>\n<li>Your browser or anti-virus is going to complain something about an &#8220;unsafe website&#8221;. Sure thing Sherlock, I signed that cert myself. Go ahead and click on &#8220;show details (or advanced)&#8221; &gt; &#8220;continue&#8221;.<\/li>\n<li>Open a new tab and access <code>https:\/\/site-b.com\/<\/code>. Do the same and access the &#8220;unsafe site&#8221;.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<div id=\"sec-cors\" class=\"sec-head\" style=\"background: #f0ffe0;\">\n<h2 class=\"in-head-d\">PHP CORS COOKIE<\/h2>\n<p>All right, let us now get into the step-by-step example of setting cookies in a CORS AJAX fetch call.<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">STEP 1) FETCH CALL FROM SITE A<\/span><\/h3>\n<div class=\"ref-head\">site-a\/1-fetch.html<\/div>\n<pre><code>&lt;!-- (A) DEMO BUTTON --&gt;\r\n&lt;input type=\"button\" value=\"CORS Cookie\" onclick=\"demo()\"&gt;\r\n \r\n&lt;script&gt;\r\nfunction demo () {\r\n  \/\/ (B) CORS FETCH TO SITE B\r\n  fetch(\"https:\/\/site-b.com\/2-handler.php\", {\r\n    mode : \"cors\",\r\n    credentials : \"include\"\r\n  })\r\n \r\n  \/\/ (C) FOR DEBUGGING, CONSOLE LOG ALL RESPONSES.\r\n  .then(res =&gt; {\r\n    console.log(res);\r\n    return res.text();\r\n  })\r\n  .then(res =&gt; console.log(res))\r\n  .catch(err =&gt; console.log(err));\r\n}\r\n&lt;\/script&gt;<\/code><\/pre>\n<p>On <code>site-a.com<\/code>, we will do a <code>fetch()<\/code> call to <code>site-b.com<\/code>. Take note of the settings:<\/p>\n<ul>\n<li><code>mode : \"cors\"<\/code> Updated browsers should automatically detect and set this&#8230; But let&#8217;s just put this here for backward compatibility.<\/li>\n<li><code>credentials : \"include\"<\/code> That is, include the use of cookies.<\/li>\n<li>Lastly, the use of <code>https:\/\/<\/code> &#8211; Very important, using <code>http:\/\/<\/code> will not allow cross-domain cookies to be set.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<div id=\"ezoic-pub-ad-placeholder-106\"><\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">STEP 2) SITE B CALL HANDLER<\/span><\/h3>\n<div class=\"ref-head\">site-b\/2-handler.php<\/div>\n<pre><code>&lt;?php\r\n\/\/ (A) GET REQUEST ORIGIN\r\nif (array_key_exists(\"HTTP_ORIGIN\", $_SERVER)) {\r\n  $origin = $_SERVER[\"HTTP_ORIGIN\"];\r\n} else if (array_key_exists(\"HTTP_REFERER\", $_SERVER)) {\r\n  $origin = $_SERVER[\"HTTP_REFERER\"];\r\n} else {\r\n  $origin = $_SERVER[\"REMOTE_ADDR\"];\r\n}\r\n \r\n\/\/ (B) CHECK ALLOWED DOMAINS\r\nif (!in_array(\r\n  parse_url($origin, PHP_URL_HOST),\r\n  [\"site-a.com\", \"site-b.com\"]\r\n)) {\r\n  http_response_code(403);\r\n  exit(\"$origin not allowed\");\r\n}\r\n \r\n\/\/ (C) PROCEED\r\nheader(\"Access-Control-Allow-Origin: $origin\");\r\nheader(\"Access-Control-Allow-Credentials: true\");\r\nsetcookie(\"It\", \"Works\", [\r\n  \"expires\" =&gt; time()+3600,\r\n  \"path\" =&gt; \"\/\",\r\n  \"domain\" =&gt; \".site-b.com\",\r\n  \"secure\" =&gt; true,\r\n  \"samesite\" =&gt; \"None\"\r\n]);\r\necho \"OK\";<\/code><\/pre>\n<p>On <code>site-b.com<\/code>:<\/p>\n<ul>\n<li><code>Access-Control-Allow-Origin<\/code> cannot be set as the wildcard <code>*<\/code>. Thus, the extra bits of code to restrict cross-domain calls from specific domains only.<\/li>\n<li><code>Access-Control-Allow-Credentials<\/code> must be set to <code>true<\/code>.<\/li>\n<li>For the cookie itself:\n<ul style=\"list-style-type: circle;\">\n<li>Take note of the <code>domain<\/code> &#8211; We are making a call from <code>site-a.com<\/code>,\u00a0but this cookie is meant for <code>site-b.com<\/code>.<\/li>\n<li>The <code>samesite<\/code> rule is set to <code>none<\/code>, so <code>secure<\/code> must be set to <code>true<\/code>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">STEP 3) <\/span><span style=\"color: #008000;\">VERIFICATION<\/span><\/h3>\n<h4><span style=\"color: #ff6600;\">DEVELOPER&#8217;S CONSOLE<\/span><\/h4>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/code-boxx.com\/wp-content\/uploads\/2022\/11\/php-cors-cookie-b-20230511.webp\" alt=\"\" width=\"500\" height=\"354\" \/><\/p>\n<p>Go ahead and click on the test button. To verify the exchange in Chromium-based browsers, open the developer&#8217;s console &gt; Network &gt; 2-handler.php &gt; Cookies.<\/p>\n<p>&nbsp;<\/p>\n<h4><span style=\"color: #ff6600;\">SITE B ITSELF<\/span><\/h4>\n<div class=\"ref-head\">site-b\/3-show.html<\/div>\n<pre><code>&lt;div id=\"demo\"&gt;&lt;\/div&gt;\r\n&lt;script&gt;\r\ndocument.getElementById(\"demo\").innerHTML = document.cookie;\r\n&lt;\/script&gt;<\/code><\/pre>\n<p>Reload <code>https:\/\/site-b.com\/3-show.html<\/code>, the cookie <code>It=Works<\/code> should also show up in the <code>&lt;div&gt;<\/code>.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"ezoic-pub-ad-placeholder-107\"><\/div>\n<p>&nbsp;<\/p>\n<div id=\"sec-extra\" class=\"sec-head\" style=\"background: #f8d5ff;\">\n<h2 class=\"in-head-d\">EXTRAS<\/h2>\n<p>That&#8217;s all for the tutorial, and here is a small section on some extras and links that may be useful to you.<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">HOW ABOUT XMLHTTPREQUEST?<\/span><\/h3>\n<p>Yes, we can set a cross-domain cookie with <code>XMLHttpRequest<\/code> too.<\/p>\n<ul>\n<li><code>var xhr = new XMLHttpRequest();<\/code><\/li>\n<li><code>xhr.withCredentials = true;<\/code><\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">COMPATIBILITY CHECKS<\/span><\/h3>\n<ul>\n<li><a href=\"https:\/\/caniuse.com\/arrow-functions\" target=\"_blank\" rel=\"noopener\">Arrow Functions<\/a> &#8211; CanIUse<\/li>\n<li><a href=\"https:\/\/caniuse.com\/fetch\" target=\"_blank\" rel=\"noopener\">Fetch<\/a> &#8211; CanIUse<\/li>\n<\/ul>\n<p>Works on all modern &#8220;Grade A&#8221; browsers.<\/p>\n<p>&nbsp;<\/p>\n<h3><span style=\"color: #008000;\">LINKS &amp; REFERENCES<\/span><\/h3>\n<ul>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\" target=\"_blank\" rel=\"noopener\">CORS<\/a> &#8211; MDN<\/li>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Fetch_API\" target=\"_blank\" rel=\"noopener\">Fetch API<\/a> &#8211; MDN<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<div id=\"sec-end\" class=\"sec-head\" style=\"background: #f1f1f1;\">\n<h2 class=\"in-head-d\">THE END<\/h2>\n<p>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!<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>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 &#8211; Read on! [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":22411,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"default","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[14],"tags":[],"class_list":["post-2123","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php"],"_links":{"self":[{"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/posts\/2123"}],"collection":[{"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/comments?post=2123"}],"version-history":[{"count":0,"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/posts\/2123\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/media\/22411"}],"wp:attachment":[{"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/media?parent=2123"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/categories?post=2123"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/code-boxx.com\/wp-json\/wp\/v2\/tags?post=2123"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}