5

I am currently trying to only return a set of CORS headers conditionally using Nginx. At first it seemed like a simple task, as I already had this working config:

upstream api-app {
  server unix:/tmp/api-app.sock fail_timeout=0;
}

server {
  listen 80;
  # [other server stuff...]

  # CORS headers added to all ALL responses of this server (needed for all requests)
  more_set_headers 'Access-Control-Allow-Methods: GET,POST,PATCH,PUT,DELETE,OPTIONS';
  more_set_headers 'Access-Control-Allow-Origin: *';
  more_set_headers 'Access-Control-Allow-Credentials: true';
  more_set_headers 'Access-Control-Request-Method: GET,POST,PATCH,PUT,DELETE,OPTIONS';
  more_set_headers 'Access-Control-Request-Headers: Content-Type';
  more_set_headers 'Access-Control-Allow-Headers: Origin,X-Requested-With,Content-Type,Accept,Session-Id,Role-Id,Visitor-Id,X-Window-Location';
  more_set_headers 'Access-Control-Expose-Headers: X-Total-Entries,X-Total-Pages,X-Page,X-Per-Page,X-Folder-Hierarchy';

  location / {
    # For OPTIONS request only return above headers and no content (also allow caching them)
    if ($request_method = 'OPTIONS') {
      # cache above (Access-Control) headers
      add_header 'Access-Control-Max-Age' 600;

      # set content length and type just for good measure:
      add_header 'Content-Length' 0;
      add_header 'Content-Type' 'text/plain charset=UTF-8';

      # return 204 - no content
      return 204;
    }

    # Forward requests to actual app (if all above conditions did not match)
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_read_timeout 35;
    proxy_send_timeout 35;

    proxy_pass http://api-app;
  }
}

So I just wanted to change the CORS header section into s.th. like this

# ...
  set $cors '';

  if ($http_origin ~ '^https?://(some.domain|some.other.domain|other-allows.domain)') {
      set $cors 'true';
  }

  if ($cors = 'true') {
      more_set_headers 'Access-Control-Allow-Methods: GET,POST,PATCH,PUT,DELETE,OPTIONS';
      more_set_headers 'Access-Control-Allow-Origin: $http_origin';
      more_set_headers 'Access-Control-Allow-Credentials: true';
      more_set_headers 'Access-Control-Request-Method: GET,POST,PATCH,PUT,DELETE,OPTIONS';
      more_set_headers 'Access-Control-Request-Headers: Content-Type';
      more_set_headers 'Access-Control-Allow-Headers: Origin,X-Requested-With,Content-Type,Accept,Session-Id,Role-Id,Visitor-Id,X-Window-Location';
      more_set_headers 'Access-Control-Expose-Headers: X-Total-Entries,X-Total-Pages,X-Page,X-Per-Page,X-Folder-Hierarchy';
  }
  

  location / {
# ...

However nginx -t quickly told me more_set_headers can't be used in an if statement. Neither can add_header.

I figured out that in the location section it would work. But I already knew this won't be good solution as we all know if is evil in nginx location sections. And I was right as then CORS headers would be lost for the OPTIONS request, I already had an if case for (an OK one, as it returns).

I also came accross the option to use Nginxs map function. But that only works if I want to alter header values, not if I want to add an entire block of headers.

So my question is the following: Is there a way to add headers conditionally (not using map) and outside the location block? Ideally allowing for an include of the CORS section so I can use it in multiple servers (i.e. nginx sites).

  • 1
    Just for completeness I found a hacky way to make this work by copying the CORS section into the location section (where if is allows) and again into the OTPIONS conditional too to return the CORS headers in that case too, but I don't want to use this in production :P – Stefan Horning Sep 08 '21 at 14:54
  • I guess for now I'll add the headers for all requesting origins – Devin Rhode Aug 07 '23 at 21:34

1 Answers1

4

I don't know if more_set_headers accepts empty strings. If it accepts, then you can define multiple map statements:

map $http_origin $cors_methods {
    default "";
    ~^https?://(some.domain|some.other.domain|other-allows.domain) Access-Control-Allow-Methods: GET,POST,PATCH,PUT,DELETE,OPTIONS;
}

map $http_origin $cors_origin {
    default "";
    ~^https?://(some.domain|some.other.domain|other-allows.domain) Access-Control-Allow-Origin: $http_origin;
}

And then use this:

more_set_headers $cors_methods;
more_set_headers $cors_origin;
Tero Kilkanen
  • 36,796
  • 3
  • 41
  • 63