Why is my Drupal 8 CORS setup not working?

2019-07-02 10:30发布

问题:

Since Drupal 8.2 the cors setup is in core. In my services.yml (and default.services.yml) I have the following setup:

cors.config:
    enabled: true
    # Specify allowed headers, like 'x-allowed-header'.
    allowedHeaders: ['x-csrf-token','authorization','content-type','accept','origin','x-requested-with']
    # Specify allowed request methods, specify ['*'] to allow all possible ones.
    allowedMethods: ['*']
    # Configure requests allowed from specific origins.
    allowedOrigins: ['*']
    # Sets the Access-Control-Expose-Headers header.
    exposedHeaders: false
    # Sets the Access-Control-Max-Age header.
    maxAge: 1000
    # Sets the Access-Control-Allow-Credentials header.
    supportsCredentials: true

My domain a.com is htaccess password protected.

On domain b.com I try to load some API from domain a.com:

$.ajaxSetup({
  xhrField: {
    withCredentials : true
  },
  beforeSend: function (xhr) {
    xhr.setRequestHeader('Authorization', 'Basic Z2VuaXVzOmNvYXRpbmdz');
  }
});

request = $.ajax({
  url: apiBaseUrl + 'api/foobar',
  dataType: 'json',
  type: 'get',
  password: 'foo',
  username: 'bar'
});

In chrome it works fine, in firefox I get an error. The request headers:

Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization

Response is 401 "Authorization required", it says request method is OPTIONS (?).

Whats wrong here?

Doing the same request in insomnia works perfectly fine.

回答1:

Response is 401 "Authorization required", it says request method is OPTIONS (?).

You need to configure your server backend to not require authorization for OPTIONS requests.

That 401 response indicates the server is requiring authorization for an OPTIONS request, but authorization is failing because the request doesn’t contain the required credentials.

The reason is, that OPTIONS request is sent automatically by your browser as part of CORS.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests explains in general why browsers make CORS preflight OPTIONS requests, but in the specific case in the question, the reason is because the request contains the Authorization request header.

So what’s happening is:

  1. Your code’s telling your browser it wants to send a request with the Authorization header.
  2. Your browser says, OK, requests with the Authorization header require me to do a CORS preflight OPTIONS to make sure the server allows requests with the Authorization header.
  3. Your browser sends the OPTIONS request to the server without the Authorization header, because the whole purpose of the OPTIONS check is to see if it’s OK to include that header.
  4. Your server sees the OPTIONS request but instead of responding to it in a way that indicates it allows the Authorization header in requests, it rejects it with a 401 since it lacks the header.
  5. Your browser expects a 200 or 204 response for the CORS preflight but instead gets that 401 response. So your browser stops right there and never tries the GET request from your code.

So you need to figure out what part of your current server-side code is causing your server to require authorization for OPTIONS requests, and you need to change that so that it instead handles OPTIONS requests without authorization being required.

Once you fix that, the existing cors.config shown in the question should cause the server to respond to the OPTIONS request in the right way—with an Access-Control-Allow-Headers response header that includes Authorization, so that your browser can then say, OK, this server allows cross-origin requests that contain the Authorization header, so I’ll now go ahead and send the actual GET request.



回答2:

Additionally to the working CORS configuration and htaccess setup:

SetEnvIfNoCase Request_Method OPTIONS noauth
Order Deny,Allow
Deny from all
Require valid-user
Allow from env=noauth
Satisfy Any

you have to make sure that your ajax setup does not have the withCredentials and password and username, like in my question. This leads to errors in Firefox, at least. Working ajax:

$.ajaxSetup({
  xhrField: {
    withCredentials : true
  },
  beforeSend: function (xhr) {
    xhr.setRequestHeader('Authorization', 'Basic foobarbarfoo');
  }
});

request = $.ajax({
  url: isApiActive ? apiBaseUrl + 'api/mydata' : './mydata.json',
  dataType: 'json',
  type: 'get'
});