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.
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:
- Your code’s telling your browser it wants to send a request with the
Authorization
header.
- 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.
- 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.
- 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.
- 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.
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'
});