A PHP CORS example

Cross Origin Resource Sharing

gearsA webpage makes a cross-origin HTTP request whenever it requests an image, stylesheet or script from a different domain than the one which served itself. A good proportion of websites on the internet rely on the ability to make such requests. However, when a cross-origin HTTP request is initiated from within a script in a webpage, web browsers block the request for security reasons. The best example is Javascript’s XMLHttpRequest function, which follows the same-origin policy. This means that a web application using XMLHttpRequest within a script can only make HTTP requests to resources located within its own domain.

The same-origin policy is well intentioned but it breaks web applications using Javascript, JSON and AJAX to access cross-domain API resources. To address this issue, most browser vendors have implemented a mechanism to allow XMLHttpRequest to make cross-domain requests: Cross Origin Resource Sharing (CORS). There is a decent technical explanation of CORS on the Mozilla website, but it is not of great use to someone actually trying to implement CORS.

As it turns out, CORS is not particularly difficult to implement, but it is difficult to find clear working examples of such implementations. CORS uses an otherwise obscure HTTP request method (OPTIONS) and several new response headers, and the most confusing aspect is figuring out which headers must be sent from where and when. These notes describe a working bare-bones example involving a simple dictionary API implemented in PHP. The code is in large part the result of trial-and-error so I make no claims for it other than the fact that it works. I hope you find it useful if you’re trying to do something similar.

Client End

Let’s start with the client end. Listing 1 is an HTML page which uses Ajax to translate the English word “two” into the Italian word “due”. A real dictionary application would probably use a form to collect the word to be translated, and a button to submit the request. But we’re not here to talk about that so we’ll cut corners: the word (“two”) is simply hard coded in the Javascript (in the request variable), and the request is automatically triggered on page load.

In the doAjax() function, the request is sent in JSON format using a standard XMLHttpRequest. The main difference between a single domain request and a cross domain request is in the headers that must be sent with the initial request.

Listing 1

<html>

<head>
<script type="text/javascript">

window.onload = doAjax();

function doAjax() {
    var url         = "http://www.example.com/api.php";
    var request     = JSON.stringify({searchterm:"two"})
    var xmlhttp     = new XMLHttpRequest();

    xmlhttp.open("POST", url);
    xmlhttp.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    xmlhttp.setRequestHeader("Access-Control-Allow-Origin", "*");
    xmlhttp.setRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
    xmlhttp.setRequestHeader("Access-Control-Allow-Headers", "Content-Type");
    xmlhttp.setRequestHeader("Access-Control-Request-Headers", "X-Requested-With, accept, content-type");

    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            var jsondata = JSON.parse(xmlhttp.responseText);
            document.getElementById("id01").innerHTML = xmlhttp.responseText;
            document.getElementById("id02").innerHTML = jsondata.word;
        }
    };

    xmlhttp.send(request);
}

</script>
</head>

<body>
<div id="id01"></div>
<div id="id02"></div>
</body>

</html>

Server End

At the other end, the API script in Listing 2 answers XMLHttpRequests. The dictionary API is simple: it translates the words one, two and three from English into Italian. The “backend” that powers this service is an associative array. Obviously a real application would replace this with calls to a database of some kind.

The important point here is that the API script needs to recognise when an initial OPTIONS request has been received, and to return the appropriate access control headers in that case (and nothing more). Following this, the browser then initiates a second request in which the real work is done.

Listing 2

<?php

$dictionary = array('one' => 'uno', 'two' => 'due', 'three' => 'tre');

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'POST') {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Headers: X-Requested-With, content-type, access-control-allow-origin, access-control-allow-methods, access-control-allow-headers');
    }
    exit;
}

$json = file_get_contents('php://input');
$obj = json_decode($json);

if (array_key_exists($obj->searchterm, $dictionary)) {
    $response = json_encode(array('result' => 1, 'word' => $dictionary[$obj->searchterm]));
}
else {
    $response = json_encode(array('result' => 0, 'word' => 'Not Found'));
}

header('Content-type: application/json');
header('Access-Control-Allow-Origin: *');
echo $response;

?>

A CLI Client

Listing 3 contains a PHP script which can be run from the command line at the client end to test the API script. This script works just fine without any CORS stuff because it uses PHP+Curl rather than Javascript in a web browser to make the request.

Listing 3

<?php

$req = array('searchterm' => $argv[1]);
$data = json_encode($req);

$curl = curl_init();
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Content-Length: ' . strlen($data))
);
curl_setopt($curl, CURLOPT_URL, 'http://www.example.com/api.php');
$result = curl_exec($curl);
$json    = json_decode($result,true);
curl_close($curl);
print_r($json['word']);
echo "\n";

?>

Assuming that this script were saved into a file named curl.php, it would be run as follows to translate the word two into Italian:

php curl.php two

Finally, could we have used JQuery for the Javascript/AJAX parts of this example? Yes, we could have. But given that native Javascript is already available in the browser and so easy to use, why would we want to load, learn and manage an unnecessary dependency like JQuery?