11

My nginx version: nginx/1.4.6

I have an issue enabling CORS for multiple subdomains. I checked https://gist.github.com/algal/5480916 and http://rustyrazorblade.com/post/2013/2013-10-31-cors-with-wildcard-domains-and-nginx/ but both solutions doesn't work for me.

It looks like the regex

if ($http_origin ~* (.*\.mydomain.com)) {
    set $cors "true";
}

is not matching and $cors is not set to "true" and therefor add_header 'Access-Control-Allow-Origin' "$http_origin" won't be executed.

I also tried with regex

$http_origin ~* (https?://.*.mydomain.com)

or

$http_origin ~* https?://.*.mydomain.com

But in either case the regex doesn't match and $cors will never set to "true".

What am I missing?

My nginx configuration - domain name in curly braces (is getting replaced by Ansible):

upstream varnish {
  server localhost:80;
}

server {
    listen 443 default;
    server_name {{vhost}};

    ssl                  on;
    ssl_certificate      /etc/ssl/certs/ssl.{{domain}}.crt;
    ssl_certificate_key  /etc/ssl/private/{{domain}}.key;

    ssl_session_timeout  5m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    #ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    #ssl_prefer_server_ciphers   on;

    # workaround remote exploit. Fixed in 1.5.0, 1.4.1
    #
    # http://mailman.nginx.org/pipermail/nginx-announce/2013/000112.html
    if ($http_transfer_encoding ~* chunked) {
        return 444;
    }

    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Host $http_host;

    proxy_redirect off;

    # CORS
    set $cors "";
    if ($http_origin ~* (.*\.{{domain}})) {
        set $cors "true";
    }

    location / {
            # Set the max size for file uploads (/admin, /webmail)
            client_max_body_size 10G;

            proxy_pass http://varnish;
            if ($cors = "true") {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow_Credentials' 'true';
                add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range, X-CSRF-Token';
                add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
            }

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

    }

    location = /50x.html {
        root   html;
    }

}
StandardNerd
  • 165
  • 1
  • 1
  • 6

5 Answers5

18

There are some unexpected things that occur when using if inside location blocks in NGINX. It's not recommended. Here is a solution that uses map. https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ and https://agentzh.blogspot.com/2011/03/how-nginx-location-if-works.html

This setup allows me to make requests to any subdomain and any port on my-domain.com and localhost (for development).

map $http_origin $allow_origin {
    ~^https?://(.*\.)?my-domain.com(:\d+)?$ $http_origin;
    ~^https?://(.*\.)?localhost(:\d+)?$ $http_origin;
    default "";
}

server {
    listen 80 default_server;
    server_name _;
    add_header 'Access-Control-Allow-Origin' $allow_origin;
    # ...
}

http://nginx.org/en/docs/http/ngx_http_map_module.html

Eric Ihli
  • 281
  • 2
  • 4
6

You can get around the limitation of only one subdomain by using this clever workaround that will allow all subdomains:

server {

    root /path/to/your/stuff;

    index index.html index.htm;

     set $cors "";

    if ($http_origin ~* (.*\.yoursweetdomain.com)) {
        set $cors "true";
    }

    server_name yoursweetdomain.com;

    location / {

        if ($cors = "true") {
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, PUT';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type';
        }


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

    }
}

Credit: http://rustyrazorblade.com/post/2013/2013-10-31-cors-with-wildcard-domains-and-nginx/

kintsukuroi
  • 161
  • 2
1

its been a year but, here is the solution that worked for me.

location / {
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  
    # Nginx doesn't support nested If statements, so we
    # concatenate compound conditions on the $cors variable
    # and process later

    # If request comes from allowed subdomain
    # (*.mckinsey.com) then we enable CORS
    if ($http_origin ~* (https?://.*\.mckinsey\.com(:[0-9]+)?$)) {
       set $cors "1";
    }
  
    # OPTIONS indicates a CORS pre-flight request
    if ($request_method = 'OPTIONS') {
       set $cors "${cors}o";
    }
  
    # Append CORS headers to any request from 
    # allowed CORS domain, except OPTIONS
    if ($cors = "1") {
       more_set_headers 'Access-Control-Allow-Origin: $http_origin';
       more_set_headers 'Access-Control-Allow-Credentials: true';
       proxy_pass      http://serverIP:serverPort;
    }
  
    # OPTIONS (pre-flight) request from allowed 
    # CORS domain. return response directly
    if ($cors = "1o") {
       more_set_headers 'Access-Control-Allow-Origin: $http_origin';
       more_set_headers 'Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE';
       more_set_headers 'Access-Control-Allow-Credentials: true';
       more_set_headers 'Access-Control-Allow-Headers: Origin,Content-Type,Accept';
       add_header Content-Length 0;
       add_header Content-Type text/plain;
       return 204;
    }
  
    # Requests from non-allowed CORS domains
       proxy_pass      http://serverIP:serverPort;
  }

Source: https://gist.github.com/bramswenson/51f0721dec22b9b258aea48b59e9a32c

criticc
  • 11
  • 1
0

$http_origin contains the value of the "origin" field in the request header.

The reason why you might have the impression that it does not work is that you tested it with a request where the "origin" header field is empty. Example: Browsers do not set the origin field on GET requests, only on POST and maybe more...

For exact info, see https://stackoverflow.com/questions/42239643/when-do-browsers-send-the-origin-header-when-do-browsers-set-the-origin-to-null

So, the code above works perfectly OK because your GET requests do not need the CORS fields in the response header. GET works without those fields!

0

Try moving the check for $http_origin into your location block.

You can see the same in the first example link you gave.

The variable is probably first filled when the location block is called.

unNamed
  • 523
  • 2
  • 10