71

As my title, here is the config file located in conf.d/api-server.conf

server {
  listen 80;
  server_name api.localhost;

  location / {
    add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
    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';
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
    }

    proxy_redirect off;
    proxy_set_header host $host;
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:3000;
  }
}

The nginx.conf file stay the same as default.

After I send request to api.localhost (api.localhost/admin/login), I still receive 405 error:

XMLHttpRequest cannot load http://api.localhost/admin/login. Response 
to preflight request doesn't pass access control check: No 'Access-
Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://admin.localhost:3000' is therefore not allowed access. 
The response had HTTP status code 405.

That is the response from server That's the request looks like

qwang07
  • 1,136
  • 2
  • 11
  • 20
  • 1
    You have not mentioned if you are facing any issue if yes then what the issue is? – Tarun Lalwani Aug 31 '17 at 18:16
  • 1
    @Tarun Lalwani I still receive 405 error when I try to send request to api.localhost, I don't know why – qwang07 Sep 01 '17 at 02:43
  • I tried the comment from and it didn't work for me with following error: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include' until i changed this line: add_header 'Access-Control-Allow_Credentials' 'true'; to: add_header 'Access-Control-Allow-Credentials' 'true'; – Themistoklis Sgouridis Apr 09 '19 at 21:50

1 Answers1

83

The issue is that your if condition is not going to send the headers in the parent in /. If you check the preflight response headers it would be

HTTP/1.1 204 No Content
Server: nginx/1.13.3
Date: Fri, 01 Sep 2017 05:24:04 GMT
Connection: keep-alive
Access-Control-Max-Age: 1728000
Content-Type: text/plain charset=UTF-8
Content-Length: 0

And that doesn't give anything. So two possible fixes for you. Copy the add_header inside if block also

server {
  listen 80;
  server_name api.localhost;

  location / {
    add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
    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';
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
      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';
      add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
    }

    proxy_redirect off;
    proxy_set_header host $host;
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:3000;
  }
}

Or you can move it outside the location block, so every request has the response

server {
   listen 80;
   server_name api.localhost;

   add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
   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';
   add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

  location / {

    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
    }

    proxy_redirect off;
    proxy_set_header host $host;
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:3000;
  }
}

If you only want to allow certain locations in your config for CORS. like /api then you should create a template conf with your headers

 add_header 'Access-Control-Allow-Origin' 'http://api.localhost';
 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';
 add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

and then use

include conf.d/corsheaders.conf;

in your OPTIONS block and /api block. So CORS are only allowed for the /api. If you don't care which location for CORS then you can use the second approach of moving core headers to server block

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • This time the response become 502 bad gateway – qwang07 Sep 01 '17 at 18:23
  • 1
    Is the 502 response means the CORS problem has been solved and the new error is about the API server? – qwang07 Sep 01 '17 at 18:57
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/153493/discussion-between-tarun-lalwani-and-qwang). – Tarun Lalwani Sep 01 '17 at 19:05
  • 1
    Problem solved, here is the [answer](https://stackoverflow.com/questions/39183357/how-to-resolve-nginx-proxy-pass-502-bad-gateway-error), thanks for your help – qwang07 Sep 02 '17 at 10:53
  • 2
    This worked great, but I didn't see the CORS headers for some of the requests, as the response code from the application was not within the allowed range. I added 'always' at the end of the CORS lines, per [Nginx Docs](http://nginx.org/en/docs/http/ngx_http_headers_module.html) and all was golden. – virtualadrian May 19 '19 at 02:34
  • after nearly two days of researching this worked for me. Sadly i can only give a single upvote - but i wish merry christmas :) – messerbill Dec 24 '19 at 13:42
  • This is great, but an explanation of why the OP's approach doesn't work as expected would be appreciated. – Roddy Apr 09 '20 at 18:00
  • @Roddy, i clearly mentioned that in my answer. OP's approach used if condition to add headers, while the outer headers don't get applied inside that if condition, so the PREFLIGHT request has no desired headers. – Tarun Lalwani Apr 09 '20 at 18:40
  • Thanks. I know (from experience!) that the headers outside the `if` don't work, but I have no idea why that is. I've never used nginx `if` before... – Roddy Apr 09 '20 at 21:55
  • This solution unfortunately doesn't work for me, I get the following error: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Code in my nginx config can be found here: https://gist.github.com/alex-r89/329adf48df118cffc79456e88cd377e6 – alexr89 May 12 '20 at 17:28
  • @alexr89, you need to run `nginx -T` and add the whole config as a gist – Tarun Lalwani May 12 '20 at 18:13
  • @TarunLalwani Hi, running that command is successful (no errors), and that is the whole config – alexr89 May 12 '20 at 18:45
  • @alexr89, i will suggest post a new question and post the link to that question here – Tarun Lalwani May 13 '20 at 03:40
  • From what I can see, duplicating headers is [not necessary](https://gist.github.com/x-yuri/5db70bbceebe13afa242ff6b4dc41cff). They are inherited. And `nginx-1.10` was released in [2016](https://nginx.org/2016.html). – x-yuri Jan 15 '21 at 23:20
  • 1
    As a note, I needed to know when the server returned status codes other than 200 and this wasn't working for me BECAUSE, NGINX needs the `always`parameter to add headers on "non successful" status. According to the [docs](http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header): If the always parameter is specified ..., the header field will be added regardless of the response code. So if you're looking for this behavior, solution is to add `always` at the end of the CORS `add_header` directives – Miguel Sánchez Villafán Jan 17 '21 at 19:59
  • 1
    That may be the reason I had to add it twice orignally as a 204 was being returned and I had not used always – Tarun Lalwani Jan 17 '21 at 20:26
  • 1
    Don't forget to add `always` to add_headers so the headers are being added for non 2XX responses. – Gustav Feb 18 '23 at 21:47