16

I am trying to allow access to every subdomain on my site in order to allow cross subdomain AJAX calls. Is there a way to specify all subdomains of a site like *.example.com or alternatively, why does the following not work when I have more than one domain listed:

header('Access-Control-Allow-Origin: http://api.example.com http://www.example.com');

I have read through the following question which appears to be similar, if not the same as this one, other than the fact that I want access to subdomains and this one refers to general domains.

Access-Control-Allow-Origin Multiple Origin Domains?

If the above question is the solution to this problem, then how am I able to retrieve the origin from the header. It appears that $_SERVER['HTTP_ORIGIN'] is very unreliable and not even cross browser. I need to be able to see the origin in any browser that may show an error when trying to send an AJAX call using javascript.

Community
  • 1
  • 1
Ben Carey
  • 16,540
  • 19
  • 87
  • 169
  • As you said, the first part of your question is answered in the link. Regarding your second question: if the browser tries an Ajax call which is forbidden by Cross Domain Policies, the request will fail and won't reach the server at all. The error will have to be handled in the browser. – Julian D. Mar 09 '12 at 09:03
  • I am aware that I will receive an error, but this error will be supplied after the call has attempted to access the external file. If the file rejects it then the error will be thrown. If I set the header to allow access to all then it will work, but this is too open for me so I would like to set it relevant to the origin of the request. Therefore, I would like to know how to get the origin of the request using PHP. – Ben Carey Mar 09 '12 at 10:02
  • Can you elaborate on what you mean by "$_SERVER['HTTP_ORIGIN'] is very unreliable and not even cross browser"? $_SERVER['HTTP_ORIGIN'] is a server-side value that doesn't get executed in the browser. – monsur Mar 09 '12 at 19:42
  • Yes, I agree and am somewhat confused by it being non cross browser compatible but I saw this stated on another question. I have never heard of HTTP_ORIGIN and only want to use it if I can be sure it will work in all browsers. – Ben Carey Mar 12 '12 at 07:45
  • http://stackoverflow.com/a/18958914/889949 – DaveRandom Sep 23 '13 at 12:15

7 Answers7

36

The solution to this issue is to use the $_SERVER['HTTP_ORIGIN'] variable to determine whether the request has come from an allowed domain, and then conditionally set the Access-Control-Allow-Origin like so:

$allowed_domains = [/* Array of allowed domains*/];

if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_domains)) {
    header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
}
Ben Carey
  • 16,540
  • 19
  • 87
  • 169
17

Here's how I did it.

The Origin header is specified by the browser and will contain the domain that requested the script on the other domain:

Origin: http://www.websiteA.com

Therefore you can "whitelist" multiple domains in your server-side script:

$allowedOrigins = [
    "http://www.websiteA.com",
    "https://www.websiteB.com"
    // ... etc
];

What you can then do is check if the $_SERVER["HTTP_ORIGIN"] global contains a domain within that whitelist:

if (in_array($_SERVER["HTTP_ORIGIN"], $allowedOrigins)) {

And set the Access-Control-Allow-Origin response header to whatever Origin header value was:

header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);

Full script:

$allowedOrigins = [
    "http://www.websiteA.com",
    "https://www.websiteB.com"
    // ... etc
];

if (in_array($_SERVER["HTTP_ORIGIN"], $allowedOrigins)) {
    header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
}
beingalex
  • 2,416
  • 4
  • 32
  • 71
  • 2
    `$_SERVER['HTTP_ORIGIN')` isn't always available, depending on the browser. You can replace that with: ```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']; }``` – Dan Smart Oct 11 '18 at 09:24
3

While the answer works, it does defeat the purpose of the whole thing, since it allows requests from any host.

I use something like:

if(isset($_SERVER['HTTP_ORIGIN'])) {
  $origin = $_SERVER['HTTP_ORIGIN'];
  if($origin == 'https://sub1.my-website.com' OR $origin == 'https://sub2.my-website.com') {
    header("Access-Control-Allow-Origin: $origin");
  }
}
Aonghus Shortt
  • 305
  • 2
  • 6
1

If you want wildcard domain, i think this is more efficient

if(isset($_SERVER['HTTP_ORIGIN']) && preg_match('!^http(s)?://([a-z0-9\-]+\.)?example\.com$!is', $_SERVER['HTTP_ORIGIN']))
{
    header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
}
FSodic
  • 73
  • 5
  • Should escape the dot in example.com, that is, example\.com is correct. Otherwise, for instance, examplexcom matched. – Sam Shaw Nov 03 '21 at 08:40
0

I tried using this approach to achieve constraint on a specific domain basis:

$allowed_origin = '';
$parts = explode('.', parse_url($_SERVER['HTTP_HOST'])['host']);
if(end($parts).".".prev($parts) === "com.domain") {
    $allowed_origin = $_SERVER['HTTP_ORIGIN'];
    header('Acesss-Control-Allow-Origin: '. $allowed_origin);
}

I hope it works.

Bishoplee
  • 39
  • 12
0
//Function to be called first in php file.
function CORS_HEADERS_HANDLER(){
  if (isset($_SERVER['HTTP_ORIGIN'])){
    switch($_SERVER['HTTP_ORIGIN']){
        //Handle an IP address and Port
      case 'http://1.2.3.4:4200':
        header('Access-Control-Allow-Origin: http://1.2.3.4:4200');
        break;
        //Handle an Website Domain (using https)
      case 'https://www.someSite.com':
        header('Access-Control-Allow-Origin: https://www.someSite.com');
        break;
        //Handle an Website Domain (using http)
      case 'http://www.someSite.com':
        header('Access-Control-Allow-Origin: http://www.someSite.com');
        break;
        //Catch if someone's site is actually the reject being cheeky
      case 'https://not.you':
        header('Access-Control-Allow-Origin: https://nice.try');
        break;
        //Handle a rejection passing something that is not the request origin.
      default:
        header('Access-Control-Allow-Origin: https://not.you');
        break;
    }
  }else{
    header('Access-Control-Allow-Origin: https://not.you');
  }
  header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
  header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
  header('Access-Control-Allow-Credentials: true');
  header('Content-Type: application/json; charset=utf-8');
  header("Cache-Control: public,max-age=3600");
  //if its an options request you don't need to proceed past CORS request.
  if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    die();
  }
}
Goce Ribeski
  • 1,352
  • 13
  • 30
0

This was my 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.

Al-Noor Ladhani
  • 2,413
  • 1
  • 22
  • 14