amazon-web-servicesnginx

CORS was working but after setting my Nginx to use HTTPS I am getting CORS error


This is my Nginx config:

map $http_origin $cors_origin {

   ~^https?://(www.)?MYDOMAIN$ $http_origin;
   default "";
}
map "$cors_origin" $cors_cred {
   ~^$ false;
   default true;
}

map "$request_method" $cors_method {
   ~^OPTIONS$ true;
   ~^GET$ true;
   ~^DELETE$ true;
   ~^PATCH$ true;
   ~^POST$ true;
   ~^PUT$ true;
   default false;
}

# echo into log to test variables
log_format upstream_time "$time_local http_origin=$http_origin remote_addr=$remote_addr allow_origin=$cors_origin cred=$cors_cred cors_method=$cors_method request_method=$request_method";

server {
  listen  80;
  server_name *.MYDOMAIN;
  return 301 https://$host$request_uri;
}
server {
  listen  443 ssl;

  ssl_certificate /etc/letsencrypt/live/api.MYDOMAIN/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.MYDOMAIN/privkey.pem;
  ssl_protocols       TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
  ssl_ciphers         HIGH:!aNULL:!MD5;

  server_name api.MYDOMAIN;

  location / {
    access_log /var/log/nginx/access.log upstream_time;

    if ($cors_origin) {
      add_header 'Access-Control-Allow-Credentials' $cors_cred always;
      add_header 'Access-Control-Allow-Origin' $cors_origin always;
      add_header 'Access-Control-Allow-Methods' 'GET, DELETE, PATCH, POST, PUT, OPTIONS' always;
      add_header 'Access-Control-Allow-Headers' 'Accept, Authorization, Keep-Alive, Origin, 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;
    }
# test
# root /var/www/html; index index.html;
    proxy_pass http://localhost:8081;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  } # End location
} # End server

I have 2 AWS EC2 instances:

  1. MYDOMAIN
  2. api.MYDOMAIN (this being a subdomain of the above domain)

They are using different SSL certs. I have security set on api.MYDOMAIN TO allow HTTPS inbound rule to come from above domain IP. I had fixed CORS previously. Since updating api.MYDOMAIN to HTTPS it's now failing CORS and I can't understand why.

I have set up the following log:

19/Aug/2024:20:45:11 +0000 http_origin=https://www.MYDOMAIN remote_addr=MYIP allow_origin=https://www.MYDOMAIN cred=true cors_method=true request_method=OPTIONS

From my terminal I have confirmed https works. - curl https://api.MYDOMAIN/healthcheck

It's just cors failing.

node-server.ts

import Fastify from "fastify";
import fs from "fs";
import path from "path";

const server = Fastify();

server.get("/healthcheck", (req, res) => {
  res.send({ message: "Success" });
});

// graceful shutdown
const listeners = ["SIGINT", "SIGTERM"];
listeners.forEach((signal) => {
  process.on(signal, async () => {
    await server.close();
    process.exit(0);
  });
});


const init = async () => {
  const options = {
    port: 8081,
  };

  server.listen(options, (err, address) => {
    if (err) {
      console.error(err);
      process.exit(1);
    }
    console.log(`Server listening at ${address}`);
  });
};

Solution

  • I've fixed it but not exactly sure why this worked. I switched the order of the nginx add_headers around.

    This then made the /healthcheck pass.

    if ($request_method = 'OPTIONS') {
    
      add_header 'Access-Control-Allow-Origin' $cors_origin;
      add_header 'Access-Control-Allow-Methods' 'GET, DELETE, PATCH, POST, PUT, OPTIONS';
      add_header 'Access-Control-Allow-Credentials' $cors_cred;
    
      # Custom headers and headers various browsers *should* be OK with but aren't
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
    
      # 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;
    }
    if ($request_method != 'OPTIONS') {
      add_header 'Access-Control-Allow-Credentials' $cors_cred always;
      add_header 'Access-Control-Allow-Origin' $cors_origin always;
      add_header 'Access-Control-Allow-Methods' 'GET, DELETE, PATCH, POST, PUT, 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;
    }
    

    Then I experimented switching the order back again to see how it failed. Yet it passed again. What!!!??

    I can't help but think its something to do with OPTIONS - add_header 'Access-Control-Max-Age' 1728000; Giving a false indication of success.

    So I continued to test and atlternate my browser to ensure I was testing a fresh uncached OPTIONS SOLUTION and changed it to 20s instead of a ridiculous 13 days.

    I found my bare minimum fix was to have to specific if conditions:

    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Methods' 'OPTIONS';
      add_header 'Access-Control-Allow-Origin' $cors_origin;
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';    
    
      # Cache 20s (max is 600s via chrome anyway)
      add_header 'Access-Control-Max-Age' 20;
      add_header 'Content-Type' 'text/plain; charset=utf-8';
      add_header 'Content-Length' 0;
    }
    if ($request_method != 'OPTIONS') {
          # all add_header stuff here..
    }
    

    Going forward if you face this problem, test in a different browser: https://www.f5.com/company/blog/nginx/avoiding-top-10-nginx-configuration-mistakes