CORS handling using a reverse proxy

K Srijeyanthan
4 min readMay 12, 2021

When we are developing a client application that connects to the backend using APIs always experiencing CORS problem in various stages such as development, testing, production.

typical CORS policy issue

Not all the backend libraries are providing the CORS handling mechanism to solve this, but understanding the fundamental cause of this error can give us a hint to solve. For example, in the above case, simply says that http://my-site.local:8088 access has been blocked, the browser was expecting the Access-Control-Allow-Origin header approved for this domain, but it was not present.

Normally if we are developing API services we should be knowing these facts on how to secure our service using CORS though it is annoying during the development stage, this article helps you to create a CORS handling layer at a proxy level rather than programming. Once the API service is ready, you will be probably ending up deploying behind any proxy and expecting some heavy lifting on service management by the proxy server

typical API deployment

Nginx is a preferred choice when it comes to reverse proxy enabling increase security, performance, and reliability to our the service we are implementing. Now assume that our API service doesn’t have any CORS handling mechanisms, so

How do we make it work and handle all the cases?

a. First we need to make sure our reverse proxy is handling HTTP OPTIONS requests, normally it is called a pre-flight request from a client application (Angular or Vue or React) to ask the server before initializing actual API service call. More detailed example of pre-flight request can be found here (https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS)

if ($request_method = OPTIONS ) {
add_header ‘Content-Type’;
add_header ‘Access-Control-Allow-Credentials’ ‘true’ always;
add_header Access-Control-Allow-Origin http://localhost:4200 always;
add_header Access-Control-Allow-Headers ‘DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header Access-Control-Allow-Methods ‘GET, POST,PUT,PATCH’;
return 200;
}

You can add the above block to handle the HTTP OPTIONS header whenever it requested from your client applications.

add_header ‘Access-Control-Allow-Credentials’ ‘true’ always;

This is necessary when your application is talking to your backend using cookies, so we should tell back to the client application saying the server is allowing credentials, and it should be always sent to the server. Normally if you are developing an application that keeps talking to the backend using APIs using cookies, then we should enable this flag, otherwise, the browser won’t send a cookie header like the following.

add_header Access-Control-Allow-Origin http://localhost:4200 always;

This header is needed when your backend service is deployed on a different domain or a different cloud or outside of your local network. If you don’t mention this header, then local development API execution will be blocked by the browser saying cross-origin as it is not matched with Host and Domain header like following.

If you are not using cookies for your API authorization, then we can specify Access-Control-Allow-Origin * , instead of specifying some particular domain. Because, Access-Control-Allow-Origin * is not allowed when cookies are used.

add_header Access-Control-Allow-Methods ‘GET, POST,PUT,PATCH’;

Here we are saying what are methods are allowed to perform API execution, If only GET is allowed, then we need to specify GET only, so other requests are blocked from the browser.

For example, the following is the Nginx site configuration for dev.example.com API service

API service from dev.example.com/api/v2/login
server{
server_name dev.example.com;
location /api/v2/ {
if ($request_method = OPTIONS ) {

add_header 'Content-Type';
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header Access-Control-Allow-Origin http://localhost:4200 always;
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header Access-Control-Allow-Methods 'GET, POST,PUT,PATCH';
return 200;
}

add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header Access-Control-Allow-Origin http://localhost:4200 always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
client_max_body_size 200M;
proxy_pass http://127.0.0.1:5623/;
}listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/dev.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}server{
if ($host = dev.example.com) {
return 301 https://$host$request_uri;
}
server_name dev.example.com;
listen 80;
}

Conclusion

Though we can implement a CORS handling from the programming level, this approach is not well-advised as we need to focus on all the CORS specifications and corner cases. We should handle this by using reverse proxies to safeguard your application API backend services.

--

--

K Srijeyanthan

I am Sri, an experienced Software Engineer and Entrepreneur, Passionate about distributed systems, and low latency application developments.