CORS and HTTP authentication

Posted on 2015-06-27 in Auto-hébergement

Before doing a request (POST, GET, PUT, …) on another domain with Javascript, web browsers will perform an OPTIONS request to verify that the request is likely to be accepted by the server. They mostly check for CORS headers.

This doesn't cause any troubles if your service doesn't require authentication: the OPTIONS request is performed without the authentication headers. So, for instance, if you are trying to connect to an owncloud instance with the webdav protocol, the OPTIONS request will failed due to a 401 unauthorized error.

To avoid that, you must make sure that when requested with the OPTIONS method, the endpoint always respond with a 200 status code and with the proper header even without authentication. You can either do it programmatically in the code of your application or with your web server configuration.

I provide the correct configuration sample for nginx and apache. You should be able to adapt them for other web server or for your favorite programming language. Both include the headers and how to return the 200 status code on options. I also provide a sample JS code that can make the request.

Nginx

add_header 'Access-Control-Allow-Methods' 'GET,OPTIONS,PUT,DELETE' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,Keep-Alive,Content-Type,accept,origin,X-Requested-With' always;

if ($request_method = OPTIONS ) {
    return 200;
}

You may also need to add the Access-Control-Expose-Headers to allow the browser to export the headers. This will be required to use them from AngularJS for instance:

add_header 'Access-Control-Expose-Headers' 'Authorization,DNT,User-Agent,Keep-Alive,Content-Type,accept,origin,X-Requested-With,Content-Disposition' always;

Apache

Header always set Access-Control-Allow-Origin "http://waffle"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Allow-Headers "Authorization,DNT,User-Agent,Keep-Alive,Content-Type,accept,origin,X-Requested-With"

RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

If you need Access-Control-Allow-Origin to be exactly the domain from the origin header, you can use:

SetEnvIf Origin "http(s)?://(.+)$" CORS=$0
Header always set Access-Control-Allow-Origin %{CORS}e env=CORS

You may also need to add the Access-Control-Expose-Headers to allow the browser to export the headers. This will be required to use them from AngularJS for instance:

Header always set Access-Control-Expose-Headers 'Authorization,DNT,User-Agent,Keep-Alive,Content-Type,accept,origin,X-Requested-With,Content-Disposition';

Javascript

Your javascript code must supply the Authorization and Content-Type headers. For instance, with the angular framework:

$http({
  method: 'POST',
  url: scope.webdav.url,
  withCredentials: true,
  headers: {
    Authorization: 'Basic ' + btoa(user + ':' + password),
    'Content-Type': 'application/vnd.google-earth.kml+xml; charset=utf-8'
  },
  data: getKml()
})