I was reading up on CORS today; apparently my previous understanding of it was flawed.
Found a worthwhile article by Remy. Also found a problem in the article in the same PHP code he offered. This was server-side code that was shown to illustrate how to handle a CORS preflight request.
The “preflight” is an HTTP OPTIONS request that the user-agent makes in some cases, to check that the server is prepared to serve a request from XmlHttpRequest. The preflight request carries with it the special HTTP Header, Origin.
His suggested code to handle the preflight was:
// respond to preflights if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // return only the headers and not the content // only allow CORS if we're doing a GET - i.e. no saving for now. if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Headers: X-Requested-With'); } exit; }
But according to my reading of the CORS spec, The Access-Control-Xxx-XXX headers should not be included in a response if the request does not include the Origin header.
See section 6.2 of the CORS doc.
The corrected code is something like this:
// respond to preflights if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // return only the headers and not the content // only allow CORS if we're doing a GET - i.e. no saving for now. if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET' && isset($_SERVER['HTTP_ORIGIN']) && is_approved($_SERVER['HTTP_ORIGIN'])) { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Headers: X-Requested-With'); } exit; }
Implementing the is_approved() method is left as an exercise for the reader!
A more general approach is to do as this article on HTML5 security suggests: perform a lookup in a table on the value passed in Origin header. The lookup can be generalized so that it responds with different Access-Control-Xxxx-Xxx headers when the preflight comes from different origins, and for different resources. This might look like this:
// respond to preflights if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // return only the headers and not the content // only allow CORS if we're doing a GET - i.e. no saving for now. if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET' && isset($_SERVER['HTTP_ORIGIN']) && is_approved($_SERVER['HTTP_ORIGIN'])) { $allowedOrigin = $_SERVER['HTTP_ORIGIN']; $allowedHeaders = get_allowed_headers($allowedOrigin); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); //... header('Access-Control-Allow-Origin: ' . $allowedOrigin); header('Access-Control-Allow-Headers: ' . $allowedHeaders); header('Access-Control-Max-Age: 3600'); } exit; }
Reference: