CORS and If Is Evil: « if » directives in nginx configuration files
While trying to configure nginx to allow Cross-Origin Resource Sharing (CORS), I stumbled upon a nasty but well-known nginx bug: If Is Evil.
In short: Do not use
if
directives in alocation
context, it can break other configurations such astry_files
and generate random 404 errors, which are almost impossible to debug.
In fact, the only two safe directives in a location
context are return
and rewrite
.
Documentation is here: https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
I needed a script served from http://domain-a.com to make an AJAX POST request to my server on http://domain-b.com. To achieve that, http://domain-a.com has to make a preflighted request: it will first send an OPTIONS request to http://domain-b.com, and will expect the response to include the header Access-Control-Allow-Origin: http://domain-a.com
.
This behavior is well documented in the HTTP specifications
At first I used the following nginx configuration:
server {
...
location / {
set $cors '';
if ($http_origin ~ '^https?://(localhost|www\.yourdomain\.com') {
set $cors 'true';
}
if ($cors = 'true') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
}
try_files $uri @other_location; # This try_files gets broken
}
....
}
I was getting a strange 404 error on the OPTIONS request.
Luckily, I found this Stack Overflow post.
I ended up using the following configuration, using a map
directive instead:
map $http_origin $cors_header {
default "";
"~^https?://(localhost|www\.yourdomain\.com" "$http_origin";
}
server {
...
location / {
add_header Access-Control-Allow-Origin $cors_header;
try_files $uri @other_location; # This try_files is working
}
...
}
This should fix the problem.
Note that with this configuration, if the
$http_origin
is not in your whitelist, theAccess-Control-Allow-Origin
will still be present in the reponse, but with an empty value, thus forbidding CORS for this request.