Set-Cookie on Browser with Ajax Request via CORS
Your AJAX request must be made with the "withCredentials" settings set to true (only available in XmlHttpRequest2 and fetch):
var req = new XMLHttpRequest();
req.open('GET', 'https://api.bobank.com/accounts', true); // force XMLHttpRequest2
req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
req.setRequestHeader('Accept', 'application/json');
req.withCredentials = true; // pass along cookies
req.onload = function() {
// store token and redirect
let json;
try {
json = JSON.parse(req.responseText);
} catch (error) {
return reject(error);
}
resolve(json);
};
req.onerror = reject;
If you want a detailed explanation on CORS, API security, and cookies, the answer doesn't fit in a StackOverflow comment. Check out this article I wrote on the subject: http://www.redotheweb.com/2015/11/09/api-security.html
I needed to pass cookies from multiple subdomains to a single API domain using AJAX and PHP and handeling CORS correctly.
This was the challenge and solution:
1 - Backend PHP on api.example.com.
2 - Multiple JS front ends such as one.example.com, two.example.com etc.
3 - Cookies needed to be passed both ways.
4 - AJAX call from multiple front-ends to PHP backend on api.example.com
5 - In PHP, I do not prefer to use $_SERVER["HTTP_ORIGIN"], not always reliable/safe in my opinion (I had some browsers where HTTP-ORIGIN was always empty).
The normal way to do this in PHP with single front end domain is starting PHP code with:
header('Access-Control-Allow-Origin: https://one.example.com');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header('Access-Control-Allow-Credentials: true');
And in JS on one.example.com domain:
jQuery.ajax({
url: myURL,
type: "POST",
xhrFields: {withCredentials: true},
dataType: "text",
contentType: "text/xml; charset=\"utf-8\"",
cache: false,
headers: "",
data: myCallJSONStr,
success: function(myResponse) {.....}
However, this is not workable as I am using multiple subdomains to call my API domain.
And this solution will NOT work as I want to pass on cookies:
header('Access-Control-Allow-Origin: *');
It conflicts with the pass on cookie setting on the JS site:
xhrFields: {withCredentials: true}
Here is what I did:
1 - use GET parameter to pass the Subdomain.
2 - Hardcode the Main domain in PHP so only (all) Subdomains are allowed.
This is the JS/JQuery AJAX part of my solution:
function getSubDomain(){
let mySubDomain = "";
let myDomain = window.location.host;
let myArrayParts = myDomain.split(".");
if (myArrayParts.length == 3){
mySubDomain = myArrayParts[0];
}
return mySubDomain;
}
And in the AJAX call:
let mySubDomain = getSubDomain();
if (mySubDomain != ""){
myURL += "?source=" + mySubDomain + "&end"; //use & instead of ? if URL already has GET parameters
}
jQuery.ajax({
url: myURL,
type: "POST",
xhrFields: {withCredentials: true},
dataType: "text",
contentType: "text/xml; charset=\"utf-8\"",
cache: false,
headers: "",
data: myCallJSONStr,
success: function(myResponse) {.....}
Finally, the PHP part:
<?php
$myDomain = "example.com";
$mySubdomain = "";
if (isset($_GET["source"])) {
$mySubdomain = $_GET["source"].".";
}
$myDomainAllowOrigin = "https://".$mySubdomain.$myDomain;
$myAllowOrigin = "Access-Control-Allow-Origin: ".$myDomainAllowOrigin;
//echo $myAllowOrigin;
header($myAllowOrigin);
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header('Access-Control-Allow-Credentials: true');
IMPORTANT, don't forget to set the cookies for all subdomains, in this case the domain for the cookie would be: .example.com (so with a dot in front of the main domain):
<?php
//////////////// GLOBALS /////////////////////////////////
$gCookieDomain = ".example.com";
$gCookieValidForDays = 90;
//////////////// COOKIE FUNTIONS /////////////////////////////////
function setAPCookie($myCookieName, $myCookieValue, $myHttponly){
global $gCookieDomain;
global $gCookieValidForDays;
$myExpires = time()+60*60*24*$gCookieValidForDays;
setcookie($myCookieName, $myCookieValue, $myExpires, "/", $gCookieDomain, true, $myHttponly);
return $myExpires;
}
This solution allows me to call the API on api.example.com from any subdomains on example.com.
NB. for situation where there is only a single calling subdomain, I prefer using .htaccess for setting CORS instead of PHP. Here is an example of .htaccess (linux/apache) for only one.example.com calling api.example.com:
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://one.example.com"
Header set Access-Control-Allow-Headers "Origin, Content-Type, X-Auth-Token"
Header set Access-Control-Allow-Credentials "true"
</IfModule>
And place this .htaccess in the root of api.example.com.
I had a similiar problem, and it turned out that the browser settings were blocking third-party cookies (Chrome > Settings > Advanced Settings > Privacy > Content Settings > Block third-party cookies and site data). Unblocking solved the problem!