Setting a cookie on a subdomain from an ajax request

13,709

Solution 1

Set the allow Credentials header on api

Access-Control-Allow-Credentials: true

Use withCredentials for the request

$.ajax({
    url: a_cross_domain_url,
    xhrFields: { 
        withCredentials: true 
    }
});

Otherwise the XMLHttpRequest will not send the cookies, regardless of the Access-Control-Allow-Credentials header.

Remove the wildcard on Access-Control-Allow-Origin

Access-Control-Allow-Origin: http://www.example.com

The wildcard * will not work. The browser will discard the response if withCredentials was set.

References:

http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/

https://developer.mozilla.org/en-US/docs/HTTP_access_control

http://arunranga.com/examples/access-control/credentialedRequest.html

http://api.jquery.com/jQuery.ajax/

Solution 2

I added this post later as I ran into problems with my previous solution when I needed to pass cookies from multiple subdomains to a single API domain using AJAX and PHP

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.

Solution 3

In addition to the correct answer of Alexandre, this is my working CORS setting via .htaccess.

Added to .htaccess file on api.example.com in the directory with the phps (or above):

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://www.example.com"
    Header set Access-Control-Allow-Headers "Origin, Content-Type, X-Auth-Token"
    Header set Access-Control-Allow-Credentials "true"
</IfModule>

And on www.example.com, I added to my AJAX call xhrFields: {withCredentials: true}. Here are all the settings:

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) { etc....

Where URL makes a call to a php on api.example.com.

Please note that Alexandre is right, you must specify an exact subdomain, any wildcards unfortunately will not work. So this will NOT work:

Header set Access-Control-Allow-Origin "*"

This setting will be blocked by CORS when using the Access-Control-Allow-Credentials "true" setting.

Share:
13,709

Related videos on Youtube

Alexandre
Author by

Alexandre

Updated on September 15, 2022

Comments

  • Alexandre
    Alexandre over 1 year

    I have a webapp on www.example.com and an API on api.example.com.

    The webapp makes ajax calls to the API.

    Somewhere I need to put a cookie on api.example.com to keep sessions track.

    To track my problem I've set test cookies on both subdomaines from the webapp and the api. The webapp set a cookie on .exemple.com and the api set one on .exemple.com and another on api.exemple.com. Cookies are set using Domain=.exemple.com only. No path, no HTTPOnly.

    Note: In the end I need only one on api.exemple.com. But theses are for the tests.

    Direct queries using my browser (Firefox 16) works fine. Query on api: the two cookies are set and sent. Query on www: the cookie is set and the two from the api is sent too. (Provided I query www after the api, of course).

    Now, I clean the browser cookies and query www only. Query on www: works fine, same as before. Subquery on api, from www's ajax request: no cookies are sent. Set-Cookies does nothing. Using Firebug I see the cookies in the response. But no traces of them on subsequent requests or the page informations.

    I event tried to enable the cookies log on Firefox. Absolutly no traces of the cookies from api, not even a reject notice.

    In the end I only need a way to store one cookie on api. And for that, I quite open :)

    Informations: The two servers are NodeJS. I've tried to set the cookie on the server side (Set-Cookie header), on the client side (document.cookies), manually using firebug.

    Others posts I've checked with no solutions (And many others which I don't recall):

    setting cross-subdomain cookie with javascript

    Cookies and subdomains

    Can subdomain.example.com set a cookie that can be read by example.com?