2

Several questions for the same topic on the net but nothing worked.

I have a serverXYZ running an Angular app, a backend tomcat webapp for authentication, a nginx server. Angular app on port 4200, tomcat app on 8080. Everything is on the same host.

The angular app has a environment.ts file (the commented string is one of my tests, see nginx conf below):

export const environment = {
  production: false,
//  apiUrl: 'http://serverXYZ:444/api'
  apiUrl: 'http://localhost:8080/backend'
};

The nginx conf:

    server {
        listen 444;
    
            server_name serverXYZ;

            location / {
                    proxy_pass http://localhost:4200;
                    
                    //websocket
                    proxy_set_header HOST $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_request_headers on;
                    proxy_http_version 1.0;
                    proxy_set_header Upgrade $http_upgrade;
                    proxy_set_header Connection "Upgrade";
                    proxy_read_timeout 120s;
            
                    if ($request_method = 'OPTIONS') {
                            add_header 'Access-Control-Allow-Origin' '*';
                            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                            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-Methods' 'GET, POST, OPTIONS';
                            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                    }
                    if ($request_method = 'GET') {
                            add_header 'Access-Control-Allow-Origin' '*';
                            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                    }
    
            }

            location /api {
                proxy_pass http://localhost:8080/backend;

                if ($request_method = 'OPTIONS') {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                        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-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                }
                if ($request_method = 'GET') {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                }

           }
}

The backend tomcat app has this in its web.xml:

...
<!-- Tomcat built in CORS implementation -->
        <!--  https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html -->
        <filter>
                <filter-name>CorsFilter</filter-name>
                <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
                <init-param>
                        <param-name>cors.allowed.headers</param-name>
                        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
                </init-param>
                <init-param>
                    <param-name>cors.allowed.methods</param-name>
                    <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
                </init-param>
        </filter>
        <filter-mapping>
                <filter-name>CorsFilter</filter-name>
                <url-pattern>/*</url-pattern>
        </filter-mapping>
        <!-- End of built in CORS implementation -->
...

I use my computer, open the browser, With the described conf if I connect to http://serverXYZ:444 the app shows up but I get CORS error on authentication (Failed CORS request). If instead I use the configuration with the commented string in environment.ts , the browser authentication doesn't say CORS, just 403. Of course the authentication api is tested and working.

I'm clueless, what's wrong? Also, solving this with nginx is my preferred way but not mandatory.

Edit: I add one more info, I'm launching the angular app with "ng serve --disableHostCheck=true" . I don't know if maybe the "--publicHost" option or "--host 0.0.0.0" should be used in this case?

Edit 2: Another thing I see just now, Firefox doesn't give me any error on OPTIONS request, just Cors on POST.

glass
  • 25
  • 1
  • 4

1 Answers1

0

Have you already seen the famous If is Evil article? The article states that

The only 100% safe things which may be done inside if in a location context are:

return ...;
rewrite ... last;

For the most cases those clarifications are too strict, usually you can safely use any nginx directive from ngx_http_rewrite_module within the if block. However using any other directive including the add_header one is really unsafe and can lead to the unpredictable results. Here is how I would write such a configuration:

map $request_method $route {
    GET        main;
    POST       main;
    OPTIONS    options;
    default    invalid;
}

map $request_method $api {
    GET        api;
    POST       api;
    OPTIONS    options;
    default    invalid;
}

server {
    listen 444;
    server_name serverXYZ;

    location / {
        try_files /dev/null @$route;
    }

    location /api {
        try_files /dev/null @$api;
    }

    location @main {
        # websocket conntection setup
        proxy_set_header HOST $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_request_headers on;
        proxy_http_version 1.0;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_read_timeout 120s;
        # pass the request
        proxy_pass http://localhost:4200;
        # add response CORS headers
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    }

    location @api {
        # transform the URI        
        rewrite ^/api(.*) /backend$1 break;
        # pass the request
        proxy_pass http://localhost:8080;
        # add response CORS headers
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    }

    location @options {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
    }

    location @invalid {
        # method not allowed
        add_header Allow "GET, POST, OPTIONS";
        return 405;
    }
}
Ivan Shatsky
  • 2,360
  • 2
  • 6
  • 17
  • Thanks a lot for these info, sadly the behaviour didn't change. – glass Nov 06 '20 at 08:22
  • After long debugging, turns out the problem wasn't here but on the tomcat running on server side. In any case your answer has some useful info & example that might help people trying to proxy, so I accept it. – glass Nov 06 '20 at 16:51
  • @glass Thanks, have you finished up using nginx config like I proposed in my answer? – Ivan Shatsky Nov 06 '20 at 19:31
  • Yes I did and it works. It avoids the potential issues described in that official nginx article. – glass Nov 07 '20 at 17:23
  • @glass Looking closer at [HTTP 405 Method Not Allowed](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405) specification turns out you should add the [`Allow`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Allow) header to the response, so I added an additional line to the `@invalid` location (check the answer edit history). – Ivan Shatsky Nov 07 '20 at 17:32