An Idiosyncratic Blog

The tale of bypassing CORS

Published on
6 minutes read

Making an API request to get data and display it to the user is the common feature in most, if not all, of the front-end applications I've built. But it's not always sunshine and rainbows. Oh the frustration when browser decides to throw a CORS error!

Ron Swanson dumping a computer
Ron Swanson dumping a computer

What the heck is CORS?

Cross-origin resource sharing (CORS) is a mechanism that allows to share resources outside of the domain from which the resource is first shared.1

Suppose a user visits http://www.example.com and the page attempts a cross-origin request to fetch the user's data from http://service.example.com. A CORS-compatible browser will attempt to make a cross-origin request to service.example.com as follows.

  • The browser sends the GET request with an extra Origin HTTP header to service.example.com containing the domain that served the parent page:

    Origin: http://www.example.com
    

The server at service.example.com may respond with:

  • The requested data along with an Access-Control-Allow-Origin (ACAO) header in its response indicating the requests from the origin are allowed. For example in this case it should be:

    Access-Control-Allow-Origin: http://www.example.com
    
  • The requested data along with an Access-Control-Allow-Origin (ACAO) header with a wildcard indicating that the requests from all domains are allowed:

    Access-Control-Allow-Origin: *
    
  • An error if the server does not allow a cross-origin request

When performing certain types of cross-domain Ajax requests, modern browsers that support CORS will initiate an extra "preflight" request to determine whether they have permission to perform the action. Cross-origin requests are preflighted this way because they may have implications to user data.

OPTIONS /
Host: service.example.com
Origin: http://www.example.com
Access-Control-Request-Method: PUT

If service.example.com is willing to accept the action, it may respond with the following headers:

Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: PUT, DELETE

The browser will then make the actual request. If service.example.com does not accept cross-site requests from this origin then it will respond with error to the OPTIONS request and the browser will not make the actual request.

CORS is essentially controlled by the Access-Control-Allow-Origin (ACAO) header on server, and nothing you do on the client can bypass this restriction.

How does a CORS Proxy work?

Rather than the browser sending a request to the target server directly, we will instead send the request to a CORS proxy with the target URL, which might look like http://corsproxy.local.dev/http://service.example.com (using the target URL as a path). The CORS proxy then forwards the request to the real server, and then returns the response plus the correct CORS headers.

This works because from the browser's point of view the content now comes from the CORS proxy's origin, that means the response will never contain a specific Access-Control-Allow-Origin (ACAO) value. This lets you make requests to servers that don't support CORS, which is lovely.

Building a CORS Proxy

I'll use http-proxy package to create a simple HTTP server which proxies the request from the browser to the target server.

  • First step is to install the package: npm install http-proxy --save

  • Next a couple lines to forward requests:

    const http = require('http'),
      httpProxy = require('http-proxy')
    
    const enableCors = (req, res) => {
      if (req.headers['access-control-request-method']) {
        res.setHeader('access-control-allow-methods', req.headers['access-control-request-method'])
      }
    
      if (req.headers['access-control-request-headers']) {
        res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers'])
      }
    
      if (req.headers.origin) {
        res.setHeader('access-control-allow-origin', req.headers.origin)
        res.setHeader('access-control-allow-credentials', 'true')
      }
    }
    
    //
    // Create your proxy server and set the target in the options.
    //
    const proxy = httpProxy.createProxyServer({ target: 'http://service.example.com' }).listen(8080)
    
    // set header for CORS when we get response from the target server
    proxy.on('proxyRes', function (proxyRes, req, res) {
      enableCors(req, res)
    })
    

The magic happens in the enableCors method which overrides the Access-Control-Allow-Origin (ACAO) header value sent from the target server.

This was a very trivial example, and definitely not to be used in production. There are quite a few tools you can use to implement a CORS proxy, from modules to easily run your own proxy like cors-anywhere, to examples you can deploy in seconds on CloudFlare workers, to a variety of hosted CORS proxies.

A bit about Security

CORS proxies are safe only if you use them very carefully. There are good reasons to use them, and safe ways to do so, but if you use them wrong you can create a whole world of new security problems.2

Using free hosted CORS proxy is like accepting candy from a stranger. If you want to use a CORS proxy to bypass a CORS restriction, don't use someone else's CORS proxy server.

A CORS proxy can read and do anything with the full request & response of all traffic through it. (As shown in the example above). While the browser will treat the request as secure (assuming the proxy uses HTTPS) it's only as secure as the proxy itself. If the "free CORS proxy" is compromised, it means all your services using that proxy are also compromised. Not cool!

If you plan on implementing a CORS proxy, you should define an allow-list of valid origins which can access your server, and error out if the request is from an invalid origin.

Conclusion

Frameworks like Angular, and Create React App provide an out-of-box solution to bypass CORS restriction during development. You would want to create a custom CORS proxy when you want to customize the request being sent to the target server or add some additional meta-data to the response from the target server.

CORS proxies can be very useful, but always make sure you lock them down tightly to allow only the use case you need, block cookies and credentials, and avoid "free hosted proxies" for any kind of production deployments.

Footnotes

  1. https://fetch.spec.whatwg.org/#http-cors-protocol

  2. CORS-anywhere: The Dangers of Misconfigured Third-Party Software