Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Slightly tighter CORS config for nginx
#
# Slightly tighter CORS config for nginx
#
# A modification of https://gist.github.com/1064640/ to include a white-list of URLs
#
# Despite the W3C guidance suggesting that a list of origins can be passed as part of
# Access-Control-Allow-Origin headers, several browsers (well, at least Firefox)
# don't seem to play nicely with this.
#
# To avoid the use of 'Access-Control-Allow-Origin: *', use a simple-ish whitelisting
# method to control access instead.
#
# NB: This relies on the use of the 'Origin' HTTP Header.
location / {
if ($http_origin ~* (whitelist\.address\.one|whitelist\.address\.two)) {
set $cors "true";
}
# Nginx doesn't support nested If statements. This is where things get slightly nasty.
# Determine the HTTP request method used
if ($request_method = 'OPTIONS') {
set $cors "${cors}options";
}
if ($request_method = 'GET') {
set $cors "${cors}get";
}
if ($request_method = 'POST') {
set $cors "${cors}post";
}
if ($cors = "true") {
# Catch all incase there's a request method we're not dealing with properly
add_header 'Access-Control-Allow-Origin' "$http_origin";
}
if ($cors = "trueget") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($cors = "trueoptions") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($cors = "truepost") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
@algal

This comment has been minimized.

Copy link

algal commented Apr 29, 2013

This is very great. I commented it, updated various parts (e.g., not returning preflight response headers to actual requests), and put the result here: https://gist.github.com/algal/5480916 .

@Ry4an

This comment has been minimized.

Copy link

Ry4an commented Aug 9, 2013

Thanks for this. I think you want a $ at the end of the regex on line 18. Without it my site http://whitelist.address.one.evil.com will pass and show up in the Access-Control-Allow-Origin header.

@jmenchacavr

This comment has been minimized.

Copy link

jmenchacavr commented Aug 5, 2014

$http_origin is empty on my system. Is there some mechanism needed to have this available?

@kaitsche

This comment has been minimized.

Copy link

kaitsche commented Nov 18, 2014

curl -I -X OPTIONS -H "Origin: http://www.example.com" http://www.yourdomain.com

@spyrospph

This comment has been minimized.

Copy link

spyrospph commented Mar 12, 2015

Hi,

I have observed when I use an if statement within a location then nginx returns a 404.

So the below does not work:

if ($cors = "true") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
}

try_files $uri $uri/ /index.php?$args;

causes nginx to not reach the try_files directive and return a 404.

Anyone else with this issue?

@mox601

This comment has been minimized.

Copy link

mox601 commented Mar 17, 2015

@spyrospph I have the same problem, and it's also documented at IfIsEvil.
imho, if there are no ways to add cors headers without using ifs, don't use nginx.

@thoughtless

This comment has been minimized.

Copy link

thoughtless commented Mar 18, 2015

@laijiang19

This comment has been minimized.

Copy link

laijiang19 commented Feb 26, 2016

What does 'X-Mx-ReqToken' header actually do? I see it in all CORS tutorials for nginx but can't find any info otherwise.

@c-shang

This comment has been minimized.

Copy link

c-shang commented Jan 11, 2017

This is very helpful

@bfricka

This comment has been minimized.

Copy link

bfricka commented Jan 4, 2018

According to nginx, if shouldn't be used in a location block like this.

@thiagof

This comment has been minimized.

Copy link

thiagof commented Feb 13, 2018

Correct ^
All this ifs are different levels and should not work together according to add_header usage docs.

@michalzvolanek

This comment has been minimized.

Copy link

michalzvolanek commented Mar 29, 2018

Very well, thanks

@ghost

This comment has been minimized.

Copy link

ghost commented Oct 30, 2018

Thanks for this. I think you want a $ at the end of the regex on line 18. Without it my site http://whitelist.address.one.evil.com will pass and show up in the Access-Control-Allow-Origin header.

+1 came here to say this

@cnjax

This comment has been minimized.

Copy link

cnjax commented Jul 24, 2019

it is great,thank you ,it works.

@mrbrazzi

This comment has been minimized.

Copy link

mrbrazzi commented Jan 15, 2020

Because the use of if is evil inside location like @mox601 and @bfricka said before, this was my solution::

location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

location ~ \.php$ {
    try_files $uri /index.php =404;
    fastcgi_pass php-upstream;
    fastcgi_index index.php;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    #fixes timeouts
    fastcgi_read_timeout 600;
    include fastcgi_params;

    if ($request_method = "OPTIONS") {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Allow-Methods 'DELETE,GET,OPTIONS,POST,PUT';
        add_header Access-Control-Allow-Headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-Token-Auth,X-Mx-ReqToken,X-Requested-With';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;

        return 204;
    }

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Methods 'DELETE,GET,OPTIONS,POST,PUT';
    add_header Access-Control-Allow-Headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-Token-Auth,X-Mx-ReqToken,X-Requested-With';
}
@mbrowne

This comment has been minimized.

Copy link

mbrowne commented Jun 10, 2020

I couldn't even get if to work at all without a return at the end, which didn't work for me because I needed to do proxy_pass. So this is the solution I ended up with, using map:

    # If the request is from an allowed domain or subdomain of that domain,
    # we set the values of the CORS headers to be ued below
    map $http_origin $use_cors {
      '~*^https?://(.+\.)*(whitelist\.address\.one|whitelist\.address\.two)' 'true';
    }
    map $use_cors $allow_origin {
      'true' $http_origin;
    }
    map $use_cors $allow_methods {
      'true' 'GET, OPTIONS, POST';
    }
    map $use_cors $allow_headers {
      'true' 'User-Agent,Keep-Alive,Content-Type,Pragma,Cache-Control,Upgrade-Insecure-Requests';
    }

    server {
      ...

      location / {
        # This prevents OPTIONS requests from failing
        if ($request_method = OPTIONS) {
          return 204;
        }

        # Allow ajax requests from other domains and subdomains
        add_header Access-Control-Allow-Origin $allow_origin;
        add_header Access-Control-Allow-Methods $allow_methods;
        add_header Access-Control-Allow-Headers $allow_headers;

        # Your other config here...
      }
    }
@programarivm

This comment has been minimized.

Copy link

programarivm commented Jul 12, 2020

If you're using PHP this basic set up may help.

default.conf

server {
    listen 443 ssl;
    server_name             pgn-chess-data.local;
    ssl_certificate         /etc/nginx/ssl/pgn-chess-data.local.crt;
    ssl_certificate_key     /etc/nginx/ssl/pgn-chess-data.local.key;
    ssl_ciphers             EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
    ssl_protocols           TLSv1.1 TLSv1.2;

    root /usr/share/nginx/pgn-chess-data/public;

    client_max_body_size 20M;

    location ~ ^/api/ {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass php_fpm:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

public/index.php

<?php

require realpath(dirname(__FILE__)) .'/../src/bootstrap.php';

header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Methods: GET, POST');
header("Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range");

switch (true) {
    case '/api/query' === $_SERVER['REQUEST_URI'] && $_SERVER['REQUEST_METHOD'] === 'POST':
        require APP_PATH . '/src/Api/Query.php';
        exit;
    case $_SERVER['REQUEST_METHOD'] === 'OPTIONS':
        http_response_code(204);
        exit;
    default:
        http_response_code(404);
        exit;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.