How to enable and validate CORS in nginx

How to enable and validate CORS in nginx

nginx_cors

Here in this article we will see how we can enable CORS headers in nginx web server and validate how the requests are restricted based on the CORS headers using an example application consisting of client with javascript triggering a request to backend service which further triggers a proxypass request to flask application.

Test Environment

Fedora Workstation 37
Nginx 1.22.1

What is CORS

As per MDN Docs Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a “preflight” request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.

If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.

Procedure

Step1: Ensure Nginx service installed and running

As a first step ensure that you have nginx installed and its up and running.

[admin@fedser nginx]$ sudo dnf install nginx

[admin@fedser nginx]$ sudo systemctl start nginx.service 
[admin@fedser nginx]$ sudo systemctl status nginx.service 

Step2: Launch Microservices Flask Applications

Here in this step we are going to install python flask package and create a flask based microservices application. This application will be serving on port 2121 with context /postjson. It’s a very basic application which returns a “Hello Flask” response.

Ensure flask python package is installed

[admin@fedser flask]$ pip install Flask

Create a flask based application which accepts JSON payload data

[admin@fedser flask]$ cat backend.py 
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/getjson', methods=['GET'])
def get_json():
    if(request.method == 'GET'):
        data = {
            "Message" : "Hello Flask"
        }
        return jsonify(data)

Launch flask application

[admin@fedser flask]$ flask --app backend run --host=fedser.stack.com --port=2121 &

Verify flask application

[admin@fedser flask]$ curl -X GET -H "Content-type: application/json" 'http://fedser.stack.com:2121/getjson'
192.168.29.117 - - [05/Apr/2023 14:36:02] "GET /getjson HTTP/1.1" 200 -
{"Message":"Hello Flask"}

Step3: Update Nginx Config

Here we are going to create two server section, one listening on standart port 80 which proxies the request on /proxyflask context to backend flask based application running on port 2121 and the other server section which listens on port 3000. This server we will be using as an client from which will try to do cross origin request to the server listening on port 80.

[admin@fedser nginx]$ cat nginx.conf
...
server {
	location /proxyflask {
		proxy_pass http://fedser.stack.com:2121/postjson;
	}
    }
    
server {
        listen 3000;
        location / {
                root /data/www;
        }
}
...

Restart the nginx service once configuration changes done.

[admin@fedser nginx]$ sudo systemctl restart nginx.service 

Validate your proxypass request as shown below.

[admin@fedser flask]$ curl -X GET -H "Content-type: application/json" 'http://fedser.stack.com/proxyflask'
192.168.29.117 - - [05/Apr/2023 14:38:10] "GET /getjson HTTP/1.0" 200 -
{"Message":"Hello Flask"}

Step3: Enable CORS for Context proxyflask

Here in this step we are going to enable CORS for context /proxyflask by setting the following header “Access-Control-Allow-Origin “fedser.stack.com”. This will ensure that the context is accessible only from the context “fedser.stack.com”.

[admin@fedser nginx]$ cat nginx.conf
...
server {
	location /proxyflask {
		add_header Access-Control-Allow-Origin "fedser.stack.com";
		proxy_pass http://fedser.stack.com:2121/postjson;
	}
    }

server {
        listen 3000;
        location / {
                root /data/www;
        }
}
...

Restart Nginx service

[admin@fedser nginx]$ sudo systemctl restart nginx.service 

Step4: Create a Javascript for XMLHTTPRequest

This is a very basic HTML file which triggers an XMLHTTPRequest to the context “/proxyflask”.

[admin@fedser flask]$ cat /data/www/corstest.html 
<!DOCTYPE html>
<html>
<body>

<h2>Using the XMLHttpRequest Object</h2>

<div id="demo">
<button type="button" onclick="loadXMLDoc()">Change Content</button>
</div>

<script>
function loadXMLDoc() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("demo").innerHTML =
      this.responseText;
    }
  };
  xhttp.open("GET", "http://fedser.stack.com/proxyflask", true);
  xhttp.send();
}
</script>

</body>
</html>

Step5: Access the corstest.html and click on Change Content button

URL – http://fedser.stack.com:3000/corstest.html

Error – Access to XMLHttpRequest at ‘http://fedser.stack.com/proxyflask’ from origin ‘http://fedser.stack.com:3000’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header has a value ‘http://fedser.stack.com’ that is not equal to the supplied origin.

As the context /proxyflask in enabled with CORS header ‘Access-Control-Allow-Origin “fedser.stack.com”‘, it allows access from origin “fedser.stack.com” but our origin is “fedser.stack.com:3000”. To allow access from this origin we need to updated the header context in nginx.conf as shown below and restart the nginx service.

[admin@fedser nginx]$ cat nginx.conf
...
server {
	location /proxyflask {
		add_header Access-Control-Allow-Origin "fedser.stack.com:3000";
		proxy_pass http://fedser.stack.com:2121/postjson;
	}
    }

server {
        listen 3000;
        location / {
                root /data/www;
        }
}
...

Restart the Nginx service once necessary changes done and retest

Also we can check the CORS headers that are enabled for our context “proxyflask” using the curl request as shown below.

[admin@fedser flask]$ curl -v http://fedser.stack.com/proxyflask
*   Trying 192.168.29.117:80...
* Connected to fedser.stack.com (192.168.29.117) port 80 (#0)
> GET /proxyflask HTTP/1.1
> Host: fedser.stack.com
> User-Agent: curl/7.85.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.22.1
< Date: Wed, 05 Apr 2023 09:24:23 GMT
< Content-Type: application/json
< Content-Length: 26
< Connection: keep-alive
< Access-Control-Allow-Origin: http://fedser.stack.com:3000
< 
{"Message":"Hello Flask"}
* Connection #0 to host fedser.stack.com left intact

Hope you enjoyed reading this article. Thank you..