/ Unix

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 a location context, it can break other configurations such as try_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.

CORS POST

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, the Access-Control-Allow-Origin will still be present in the reponse, but with an empty value, thus forbidding CORS for this request.