Skip to content

Instantly share code, notes, and snippets.

@Stanback
Forked from michiel/cors-nginx.conf
Last active October 1, 2024 23:28
Show Gist options
  • Save Stanback/7145487 to your computer and use it in GitHub Desktop.
Save Stanback/7145487 to your computer and use it in GitHub Desktop.
Example Nginx configuration for adding cross-origin resource sharing (CORS) support to reverse proxied APIs
#
# CORS header support
#
# One way to use this is by placing it into a file called "cors_support"
# under your Nginx configuration directory and placing the following
# statement inside your **location** block(s):
#
# include cors_support;
#
# As of Nginx 1.7.5, add_header supports an "always" parameter which
# allows CORS to work if the backend returns 4xx or 5xx status code.
#
# For more information on CORS, please see: http://enable-cors.org/
# Forked from this Gist: https://gist.github.com/michiel/1064640
#
set $cors '';
if ($http_origin ~ '^https?://(localhost|www\.yourdomain\.com|www\.yourotherdomain\.com)') {
set $cors 'true';
}
if ($cors = 'true') {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
# required to be able to read Authorization header in frontend
#add_header 'Access-Control-Expose-Headers' 'Authorization' always;
}
if ($request_method = 'OPTIONS') {
# 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;
}
@piotrekkr
Copy link

Hello @RohitSharma2k122 . Seems like error message indicates problem: Redirect is not allowed for a preflight request . Probably, when browser asks for CORS headers using OPTIONS method it gets redirect response (http => https maybe?).

As I said, you can test those responses by

  1. enabling OPTIONS request in browser dev tools
  2. copy OPTION request as CURL (right click menu in networking tab in dev tools)
  3. Adding -i option to curl command copied before and running it in cli like curl -i .......

It will show you response headers and body of OPTIONS request made by browser.

Btw, you probably should post this question on stackoverflow and not here.

@RohitSharma2k122
Copy link

Thanks you so much @piotrekkr
CORS issue is resolved by adding proxy_set_header Authorization in location tab.

server {
root /usr/share/nginx/www;
index index.html index.htm;
location /grafana/
{
include cors-settings.conf;
rewrite ^/grafana/(.*) /$1 break;
proxy_set_header Host $http_host;
proxy_pass http://grafana;
proxy_set_header Authorization "Basic YWRtaW";
}

@salvationws
Copy link

salvationws commented Sep 8, 2022

Hello. @piotrekkr
Can't make CORS work. Can you help me?
Here is my configuration of Nginx without CORS params.

The backend is HTTP and the Frontend is HTTPS

server {
    listen 80;
    server_name             example-frontend.com www.example-frontend.com;
    return 301              https://example-frontend.com$request_uri;
    }

server {
    listen 443 ssl http2;
    server_name             example-frontend.com www.example-frontend.com;
    # Configuration for SSL/TLS certificates
    ssl_certificate         /etc/letsencrypt/ssl/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/ssl/privkey.pem;
    ssl_stapling            on;
    ssl_session_timeout     1d;
    ssl_session_cache       shared:SSL:50m;
    ssl_session_tickets     off;
    # Disable insecure TLS versions
    ssl_protocols           TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers             EECDH:+AES256:-3DES:RSA+AES:RSA+3DES:!NULL:!RC4;
    # Logs
    access_log              /var/log/nginx/nginx.access.log;
    error_log               /var/log/nginx/nginx.error.log;
    
    
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    location /api/ {
        proxy_set_header    X-Forwarded-Host    $host:$server_port;
        proxy_set_header    X-Forwarded-Server  $host;
        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   $scheme;
        proxy_pass          http://example-backend:8080/;
    }
        
    location / {
        root /app;
        try_files $uri $uri/ /index.html =404;
    }
}

@shlomo7492
Copy link

Hello guys!

We have got working config only with the following trick:

`

if ( $request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|PATCH|DELETE)$ ) {
    return 444;
}
    
set $origin $http_origin;

if ($origin !~ '^https?://(subdom1|subdom2)\.yourdom\.zone$') {
    set $origin 'https://default.yourdom.zone';
}

if ($request_method = 'OPTIONS') {

    add_header 'Access-Control-Allow-Origin' "$origin" always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Accept, Authorization' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    add_header Access-Control-Max-Age 1728000;
    add_header Content-Type 'text/plain charset=UTF-8';
    add_header Content-Length 0;
    return 204;
}
    
if ($request_method ~ '(GET|POST|PATCH|PUT|DELETE)') {
    add_header Access-Control-Allow-Origin "$origin" always;
    add_header Access-Control-Allow-Methods 'GET, POST, PATCH, PUT, DELETE, OPTIONS' always;
    add_header Access-Control-Allow-Headers 'Content-Type, Accept, Authorization' always;
    add_header Access-Control-Allow-Credentials true always;
}

`

if block can be implemented with map may be it can help somebody

works, the best solution so far!!!

@aamirmalek
Copy link

one silly question: I have nginx ingress which I installed using helm charts. Any idea where to put this annotation or snippet???

Curtly facing issue "Access to XMLHttpRequest at 'https://xxxxx/tenant/dashboard/ml/project?startDate=2021-11-23T09%3A37%3A16-06%3A00&endDate=2022-11-23T09%3A37%3A16-06%3A00&filterby=InProgress&page=1&offset=5' from origin 'https://xxxxxxxxx.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

@mylastore
Copy link

mylastore commented Jan 24, 2023

Justin Fortier

I am trying to do the same as you.
My BE is api.defaultseo.com, and FE defaultseo.com
When I log in from the FE, the refreshToken and Token are set on the BE only
I want to send the tokens to the FE on the response header
so when the FE requests access to a restricted BE route, FE sends the Token & Refresh Token to the BE.

Would you be able to help me fix the above? I also use MyVesta control panel to host the two websites on a droplet with Digital Ocean.

@diegoazh
Copy link

+1 for @mPanasiewicz 👍🏼

@mylastore
Copy link

Justin Fortier

I am trying to do the same as you. My BE is api.defaultseo.com, and FE defaultseo.com When I log in from the FE, the refreshToken and Token are set on the BE only I want to send the tokens to the FE on the response header so when the FE requests access to a restricted BE route, FE sends the Token & Refresh Token to the BE.

Would you be able to help me fix the above? I also use MyVesta control panel to host the two websites on a droplet with Digital Ocean.

I figure this out. It was not related to Nginx

It was on the cookies I just added the cookie attribute: domain: 'defaultseo.com' for example that was my issue.

@bitbay
Copy link

bitbay commented Oct 14, 2023

I'm just familiarizing myself with nginx configurations, but apparently

  • if You use an add_header directive in an if statement, no other add_header from outside the if seems to apply
  • empty strings used in add_header values aren't added/applied to the response

With these in mind, i only could made my case work (different methods allowed for different origins) was defining only variables inside the if statements.

set $allowed_headers '';
set $allowed_methods '';
set $allowed_credentials '';
set $allowed_origin '';
set $type '';
set $length '';

if ( $http_origin ~ "https://put-enabled" ) {
    set $allowed_origin $http_origin;
    set $allowed_methods 'OPTIONS, PUT';
    set $allowed_credentials false;
}

if ( $http_origin ~ "https://delete-enabled" ) {
    set $allowed_headers 'content-type, X-Requested-With';
    set $allowed_origin $http_origin;
    set $allowed_methods 'OPTIONS, DELETE';
    set $allowed_credentials true;
}

if ($request_method = OPTIONS) {
    set $allowed_headers 'content-type';
    set $type 'text/plain';
    set $length 0;
}

add_header Access-Control-Allow-Origin $allowed_origin always;
add_header Access-Control-Allow-Methods $allowed_methods always;
add_header Access-Control-Allow-Headers $allowed_headers always;
add_header Access-Control-Allow-Credentials $allowed_credentials always;
add_header Content-Type $type;
add_header Content-Length $length;

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

if ($request_method = POST) {
    return 404;
}

If You wonder why the POST returns a 404, it's because the navigator (chrome at least) ignores the Access-Control-Allow-Methods for POST requests. 🤷

@Pakbon
Copy link

Pakbon commented Nov 20, 2023

I'm just familiarizing myself with nginx configurations, but apparently

* if You use an `add_header` directive in an `if` statement, no other `add_header` from outside the `if` seems to apply

That's because IF is evil

@freultwah
Copy link

Having enabled the snippet, I scanned my site with nuclei and discovered that the regex '^https?://(localhost|www\.yourdomain\.com|www\.yourotherdomain\.com)' allows for suffixing the domain with anything, as in e.g. www.yourdomain.com.honeypot.com. I amended the regex with a '$' at the end (as in www\.yourotherdomain\.com)$' and it was smooth sailing.

@malithwee
Copy link

mPanasiewicz Works like a charm! thanks

@evtic229
Copy link

Hello @mPanasiewicz & @piotrekkr i saw you master niginx. Thank you for your good job I have a vps server with nginx and tomcat10..17, my frontend is not hosted on my vps. i have deployed my api java and test it throught postman and everything work but while i tried to fetch data from the frontend android and http this issue appear
Access to fetch at 'https://api.domain.com/directives/api/v1/file=' from origin 'https://domain.dev/' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

@Johnson1s
Copy link

y81mcnb3w9p5cep8

@nabtron
Copy link

nabtron commented Apr 1, 2024

My complete script that works both on my website and localhost:

  • Avoided if, mentioned in ifisevil
  • Fixed regex vulnerability, pointed out by @ejcx above.

In this script, my server is blog.mywebsite.com. It enabled CORS mywebsite.com and localhost to access requested resource.

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name blog.mywebsite.com;
    root /var/www/ghost/system/nginx-root;

    ssl_certificate /etc/letsencrypt/blog.mywebsite.com/fullchain.cer;
    ssl_certificate_key /etc/letsencrypt/blog.mywebsite.com/blog.mywebsite.com.key;
    include /etc/nginx/snippets/ssl-params.conf;

    set $cors_origin "";
    set $cors_cred   "";
    set $cors_header "";
    set $cors_method "";

    if ($http_origin ~ '^https?://(localhost|mywebsite\.com)$') {
            set $cors_origin $http_origin;
            set $cors_cred   true;
            set $cors_header $http_access_control_request_headers;
            set $cors_method $http_access_control_request_method;
    }

    add_header Access-Control-Allow-Origin      $cors_origin;
    add_header Access-Control-Allow-Credentials $cors_cred;
    add_header Access-Control-Allow-Headers     $cors_header;
    add_header Access-Control-Allow-Methods     $cors_method;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:2368;
    }

    location ~ /.well-known {
        allow all;
    }
    client_max_body_size 50m;
}

Your solution more or less worked for me using nginx version 1.12.2 Not sure which version of nginx you were using, but I couldn't run add_headers in my server block, but could in my location block which worked fine. Thanks.

add_header can work in http context .see http://nginx.org/en/docs/http/ngx_http_headers_module.html

worked like charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment