CORS Behind Kerberos


Recently had a frustrating issue dealing with CORS when interacting with an API behind an authenticated Kerberos setup. Attempts to contact the API presented the following error:-

XMLHttpRequest cannot load http://dev:8080/api/v1/ Invalid HTTP status code 401

According to the spec the authentication header is not issued on preflight requests. Specifically:-

Otherwise, make a preflight request. Fetch the request URL from origin source origin using referrer source as override referrer source with the manual redirect flag and the block cookies flag set, using the method OPTIONS, and with the following additional constraints:

Include an Access-Control-Request-Method header with as header field value the request method (even when that is a simple method).

If author request headers is not empty include an Access-Control-Request-Headers header with as header field value a comma-separated list of the header field names from author request headers in lexicographical order, each converted to ASCII lowercase (even when one or more are a simple header).

Exclude the author request headers.

Exclude user credentials.

Exclude the request entity body.

This has the effect of the server issuing a 401 and the browser blocking the request immediately.

The solution requires explicitly disabling authentication for the preflight OPTIONS request, in this instance Kerberos.

In the .htaccess file for the project this meant adding:-

<LimitExcept OPTIONS>
  Require valid-user
</LimitExcept>

This allowed the preflight to continue and any subsequent GET or POST to the service.

After this was sorted, the next issue was the origin headers needing changed to sort this error:-

XMLHttpRequest cannot load https://dev:8080/api/v1/. A wildcard ‘*’ cannot be used in the ‘Access-Control-Allow-Origin’ header when the credentials flag is true. Origin ‘http://localhost:8080’ is therefore not allowed access

This involved changing the Access-Control-Allow-Origin header from a wildcard to the specific origin accessing the resource:-

$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
$response->headers->set('Access-Control-Allow-Credentials', 'true');

Any client side code needs changed to explicitly pass credentials when dealing with the protected service. E.g. jQuery:-

$.ajax({
  url: "https://dev:8080/api/v1",
  dataType: 'json',
  type: 'GET',
  xhrFields: {
    'withCredentials': true
  },
  crossDomain: true
}).success(function(data) {
  console.log(data)
}).error(function(xhr, status, error) {
  console.log(xhr);
});

Or Angular:-

app.config(function ($httpProvider) {
  $httpProvider.defaults.withCredentials = true; //Tell browser to provide credentials
  $httpProvider.defaults.useXDomain = true; //Take CORS measures
});

Other resources discussing this matter.