nginx error : The 'Access-Control-Allow-Origin' header contains multiple values - nginx

I used to run nginx v1.6 with this configuration :
location / {
alias /some/path/;
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_set_header uuid $uuid;
more_set_headers 'Access-Control-Allow-Origin: $http_origin';
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS';
more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,access_token,__setXHR_';
if ($request_method = 'OPTIONS') {
more_set_headers 'Access-Control-Allow-Origin: $http_origin';
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS';
more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,access_token,__setXHR_';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
Since my upgrade to nginx v1.10.x, "more_set_headers" isn't working anymore, and I've changed it by add_header 'blablabla' always;
It now looks like this :
location / {
alias /some/path/;
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_set_header uuid $uuid;
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: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,access_token,__setXHR_' always;
if ($request_method = 'OPTIONS') {
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: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,access_token,__setXHR_' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
However, when I now go on the website, I have this error :
Failed to load https://mywebsite/auth/login: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values '$http_origin: always', but only one is allowed. Origin 'https://mywebsite' is therefore not allowed access.
What should I change to make it work ? I'm a bit stuck there.

I cannot remember where I read this earlier today. I lost the page. However, the answer in my case was:
you cannot set multiple cors headers in your stack. E.g.g. nginx and django. Only one of them can do the job or there will be multiple
values in the header.
By removing cors handling from my api and only let nginx do it, the error disappeared.
I will update this and refer to the source when I find it.
Since it is really hard to find this info on Google across multiple forums and platforms, I decided to answer this obvious thread on SO.
Update 2021 - Some more explanation
For the curious ones, to elaborate a bit more on this. A CORS header can actually only container a single value. If its a * to allow all, that's ok but what if you wanted to allow two specific IPs? You would want to write those two IPs in the header, which is not valid.
What all those packages do under the hood when using web frameworks ala Express.js or Django is the following.
They match the IP from the incoming requests referrer header against a list of IPs that was provided by the developer. If there is a match, the response will get that specific IP added in the CORS header. No other IP.
If you ever want to implement your own CORS middleware, you need to replicate the described behavior.
Nginx is very likely doing the same thing while neither side checks if there is already a value in the header. They just add the value based on match. So the result will be an invalid CORS header.

But you're adding the same headers twice.
No need to add header after request method check as it's added above in any case.
So I believe your config should look like this:
location / {
alias /some/path/;
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_set_header uuid $uuid;
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: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,access_token,__setXHR_' always;
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;
}
}

Solution found.
It was "simply" a syntax mistake.
add_header 'Access-Control-Allow-Origin: $http_origin' always;
must in fact be
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
And same modification for all the others add_header entries of course.

Based on the explication of The Fool ( first answer).
I resolved the problem by block nginx to set headers by delete proxy_set_header Host $host; from nginx configuration
server {
....
location / {
...
# proxy_set_header Host $host;
}}

add_header will append value instead of replacing it

Related

Allowing CORS for api.glossgenius.com

I need assistance, i am unsure how to make the API response from "api.glossgenius.com" work properly to get elements of the site working.
https://studio21md.com/portfolio
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.glossgenius.com/v3/web/portfolio_images?slug=meganhammett. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.glossgenius.com/v3/web/portfolio_images?slug=meganhammett. (Reason: CORS request did not succeed).
Can anyone assist on Nginx how i would solve this specific issue?
Here is my config, Thank you for your assistance.
server {
listen 80;
server_name www.studio21md.com studio21md.com;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass https://meganhammett.glossgenius.com;
}
Please use this config in nginx.
server {
listen 80;
server_name www.studio21md.com studio21md.com;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
add_header 'Access-Control-Allow-Origin' 'https://api.glossgenius.com';
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' 'https://api.glossgenius.com';
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_pass https://meganhammett.glossgenius.com;
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;
}
}
As I understand you do not have access to the backend of api.glossgenius and you are calling it from the javascript.
I would try calling that glossgenius api from your backend. And make endpoint so that your frontend calls your backend. You do not have cors problems from your frontend calling your backend as I understand.

Why is Nginx proxy_pass ignoring add_headers when used with variables?

I am trying to make a dynamic CORS proxy using Nginx, so I can use multiple API's from my front-end code without any CORS errors. My current way is using multiple location blocks, one for each domain, which works, but I would rather have a dynamic solution. My idea is to use x-proxy-target to specify the upstream hostname, and pass on the pathname. Nginx is running in Docker containers on Kubernetes, hence 10.245.0.10 being the resolver.
This works perfectly, and sends the CORS headers that are set at the bottom using add_header
location / {
default_type application/json;
if ($http_x_proxy_target = "") {
return 400 '{"message": "No x-proxy-target header", "code": "PROXY_TARGET_NOT_SET"}';
}
resolver 10.245.0.10 [::1];
set $cors '*';
if ($http_origin != "") {
set $cors $http_origin;
}
if ($request_method = "OPTIONS") {
add_header Access-Control-Allow-Origin $cors always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers * always;
return 204;
}
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Expose-Headers;
proxy_hide_header Access-Control-Allow-Credentials;
proxy_hide_header Access-Control-Allow-Origin;
proxy_set_header x-proxy-target '';
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Origin https://$http_x_proxy_target;
proxy_set_header Host $http_x_proxy_target;
proxy_set_header Referer https://$http_x_proxy_target/;
proxy_pass https://google.com/;
add_header Access-Control-Allow-Headers * always;
add_header Access-Control-Expose-Headers * always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Origin $cors always;
}
But when I change the proxy_pass to use the supplied upstream:
location / {
default_type application/json;
if ($http_x_proxy_target = "") {
return 400 '{"message": "No x-proxy-target header", "code": "PROXY_TARGET_NOT_SET"}';
}
resolver 10.245.0.10 [::1];
set $cors '*';
if ($http_origin != "") {
set $cors $http_origin;
}
if ($request_method = "OPTIONS") {
add_header Access-Control-Allow-Origin $cors always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers * always;
return 204;
}
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Expose-Headers;
proxy_hide_header Access-Control-Allow-Credentials;
proxy_hide_header Access-Control-Allow-Origin;
proxy_set_header x-proxy-target '';
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Origin https://$http_x_proxy_target;
proxy_set_header Host $http_x_proxy_target;
proxy_set_header Referer https://$http_x_proxy_target/;
proxy_pass $scheme://$http_x_proxy_target$uri$is_args$args;
add_header Access-Control-Allow-Headers * always;
add_header Access-Control-Expose-Headers * always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Origin $cors always;
}
I get the correct response using Postman, but the CORS headers are not set. I have tried moving the add_header lines to above the proxy_pass, to no avail. To me, this seems like a possible bug in Nginx, although I might just be making an obvious mistake. I can't find any mention of this problem anywhere, although it is a very specific usecase.

rest api direct vs nginx proxy

I am a newbie in nginx and CORS and finding it challenging getting this right.
I have rest services hosted on a server which blocks CORS so installed nginx to proxy for rest call. What works:
rest api call (from angular code) to backend server after enabling CORS
rest api call (from chrome) to frontend nginx server which has cors enables
What doesn't work: rest api call (from angular code) to frontend nginx
I think the CORS part work as I do not see that error anymore but angular is getting a null response.
For above scenarios, I have tried using GET and POST methods. Response code is 200 OK even for failed scenario.
Here is the nginx conf:
upstream myserver {
server myserver.com:8443;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name myserver.com;
ssl_certificate /some.crt;
ssl_certificate_key /some.key;
location /rest-service/ {
# Simple requests
if ($request_method ~* "(GET|POST)") {
add_header "Access-Control-Allow-Origin" *;
}
# Preflighted requests
if ($request_method = OPTIONS ) {
add_header "Access-Control-Allow-Origin" *;
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
return 200;
}
proxy_pass_header Server;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_connect_timeout 5;
proxy_read_timeout 240;
proxy_intercept_errors on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass https://myserver/rest-service/;
proxy_ssl_trusted_certificate /some.pem;
proxy_ssl_verify off;
proxy_ssl_session_reuse on;
}
}
Here is the angular/typescript code (running from loaclhost):
ngOnInit() {
let url='https://myserver.com/rest-service/login?login=admin&password=password';
this.http.get(this.url).subscribe((response) => {console.log(response); });
}
I think I figured out the issue and posting it here; hope it helps someone.
Following worked:
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'http://localhost:4200' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Origin,Keep-Alive,User-Agent,Cache-Control,Content-Type,Accept' 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' ) {
add_header 'Access-Control-Allow-Origin' 'http://localhost:4200' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Origin,Keep-Alive,User-Agent,Cache-Control,Content-Type,Accept' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ( $request_method = 'POST' ) {
add_header 'Access-Control-Allow-Origin' 'http://localhost:4200' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Origin,Keep-Alive,User-Agent,Cache-Control,Content-Type,Accept' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}

How do multiple if in the same location block interact?

We have an hard time understanding how do multiple if statement works inside nginx:
We have a if statement that is supposed to set CORS headers when the request come from a certain domain
We have an if statement that is supposed to set some headers when the request method is OPTION.
However, when the request both has method OPTIONS and come from a certain domain, the CORS headers are not set. How do multiple if statement works in a single location context in Nginx?
server {
listen 127.0.0.1:80;
server_name myserver.com;
set $cors '';
if ($http_origin ~ '^https?://*.\.com') {
set $cors 'true';
}
location / {
proxy_pass http://myserver:9000;
if ($cors = 'true' ){
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' 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;
}
### force timeouts if one of backend is died ##
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
### Set headers ####
proxy_set_header Accept-Encoding "";
proxy_set_header Host $host:$server_port;
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;
add_header Front-End-Https on;
proxy_redirect off;
}
}
The add_header directive behaves a bit differently to other Nginx directives as it does not inherit from another configuration block if it is included in the current block.
There are a few approaches depending upon how much granularity you need in deciding what headers to add where, you could use a map directive but as it looks like you only have 3 sets of headers, one for all connections and then your two conditional sets then for simplicity you can just move your ifs to the server block.
You have quite a few headers to add, so for clarity I would save each set in it's own .conf file. Then in your server block you already have one if directive, so change that and add your other if statement, and include your proxy headers you want to be set for everyone in the server block too:
include headers/proxy-headers.conf;
if ($http_origin ~ '^https?://*.\.com') {
include headers/cors-headers.conf;
}
if ($request_method = 'OPTIONS') {
include headers/options-headers.conf;
}

Nginx proxy_pass missing error body

Below is a pretty standard nginx proxy_pass setup:
server {
listen 80;
server_name ireport.jungdigital.com;
access_log /var/log/nginx/ireport.access.log;
root /var/www/ireport.jungdigital.com/dist;
index index.html index.htm;
location / {
}
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Reques
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 = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Reques
}
if ($request_method = 'PUT') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Reques
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Reques
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-proxy true;
proxy_set_header Host ireport.somehost.org;
proxy_pass http://ireport_dyndns/api/;
proxy_ssl_session_reuse off;
proxy_redirect off;
}
}
The API that I'm proxying returns response body's that contain error information for 400, 404, and 500 error codes. For example, on a 404, my response body might look like:
{
"errorCode": "TOKEN_NOT_FOUND",
"errorMessages": [
"Could not find a matching authorization token."
]
}
If I perform the request without the proxy, I get the response bodies for the errors.
If I use the nginx proxy, for some reason the response bodies are swallowed by nginx and I can't even see a response at all in my web browser Network tab.
Is there a way to tell Nginx to return response bodies for error codes in a proxy_pass?
I run into same question recently.
And the last anwser is that: add proxy header Upgrade
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
My full config as following:
upstream cloud-api {
server 127.0.0.1:8089;
}
client_max_body_size 20M;
client_header_buffer_size 8k;
large_client_header_buffers 4 16k;
server {
listen 8001;
access_log /data/senseid-cloud-log/senseid-cloud-api-access.log;
error_log /data/senseid-cloud-log/senseid-cloud-api-error.log warn;
location / {
proxy_http_version 1.1;
proxy_set_header Host $http_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_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_read_timeout 300s;
proxy_pass http://cloud-api;
}
}
And you could catch 500/401 .etc error body
Details : http://nginx.org/en/docs/http/websocket.html
Hope to give you some help.
See How to add a response header on nginx when using proxy_pass?
Per Alexey's comment - "browsers need headers to allow access to response body. I guess, you need always flag for add_header"
Since nginx 1.7.5 you can use the keyword always to include headers even in error responses - so you can setup your nginx.conf as follow:
-
server {
server_name .myserver.com
location / {
proxy_pass http://mybackend;
add_header X-Upstream $upstream_addr always;
}
}
Today I move Laravel 5.2 from IIS 8.5 to Ubuntu 16.04 (Nginx 1.10.0 - PHP-FPM 7.0.10), I have same problem. The body response from server are always empty when request from Angular2. But the postman still get body on response.
So that must be problem with request headers.
This config help me solved problem above:
add_header 'Access-Control-Allow-Origin' '*' 'always';
add_header 'Access-Control-Allow-Credentials' 'true' 'always';
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PUT, OPTIONS, HEAD' 'always';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' 'always';
Third parameters for add_header only available in recent nginx version.

Resources