{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/javascript/cors-proxy-with-cloudflare-workers/",
"description": "Build your own CORS proxy using Cloudflare Workers to bypass browser CORS restrictions. Includes deployment with GitHub Actions automation.",
"path": "/javascript/cors-proxy-with-cloudflare-workers/",
"publishedAt": "2023-05-21T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"JavaScript",
"Networking",
"GitHub"
],
"textContent": "Cloudflare absolutely nailed the serverless function DX with [Cloudflare Workers]. However,\nI feel like it's yet to receive widespread popularity like AWS Lambda since as of now, the\nservice only offers a single runtime - JavaScript. But if you can look past that big folly,\nit's a delightful piece of tech to work with. I've been building small tools with it for a\ncouple of years but never got around to writing about the immense productivity boost it\nusually gives me whenever I need to quickly build and deploy a self-contained service.\n\nRecently, I was doing some lightweight frontend work and needed to make some AJAX calls from\none domain to another. Usually, browser's [CORS] (Cross-Origin Resource Sharing) policy will\nget in your way if you try this. While you're reading this piece, open the dev console and\npaste the following fetch snippet:\n\nThis snippet will attempt to make a GET request from <https://rednafi.com> to\n<https://mozilla.org>. However, the client's CORS policy won't allow you to make an AJAX\nrequest like this and load external resources into the current site. On your console, you'll\nsee an error message like this:\n\nThis is a good security measure. Without CORS, a malicious script could make a request to a\nserver in another domain and access the resources that the user of the page is not intended\nto have access to. So much has been said and written about CORS that I won't even attempt to\nexplain it here. Here's another [high-level introduction to CORS].\n\nCORS proxy\n\nWhile CORS is generally a good thing, it can be quite annoying when you're trying to build\nsomething that needs access to external resources. In those cases, you'll have to mess\naround with the origin server and add a few headers that the browser can understand before\nit allows you to load those external resources. But sometimes you don't have access to the\norigin server or simply don't want to deal with modifying the server's response headers\nevery time you need to access external resources. That's where CORS proxies can come in\nhandy.\n\n> A CORS proxy server acts as a bridge between your client and the target server. It\n> receives your request and forwards it to the target server with a modified origin header\n> so that the target server thinks the request is coming from the same origin as itself.\n\nThis way, you can bypass the same-origin policy of browsers and access resources from\ndifferent domains. I usually use free proxies like [cors.sh] to bypass CORS restrictions.\nYou can drop this snippet to your browser's console and this time it'll allow you to load\nthe contents of <https://mozilla.org> from <https://rednafi.com>:\n\nThe target server's response looks somewhat like this:\n\nIf you want to learn more about how CORS proxies work, here's a [fantastic article on CORS\nproxies] that explains the inner machinery in more detail.\n\nFree proxy servers can be pernicious\n\nUsing a free CORS proxy server can be dangerous as it might spill the beans on your requests\nand data to some random service you don't know or trust. Since it plays as a middleman\nbetween your app and the resource you're after, it could potentially snoop on, mess with, or\nkeep tabs on your requests and data. Plus, some of those free CORS proxy servers might have\nrestrictions on the size, type, or number of requests they can handle, or they might not\neven support HTTPS or other security bells and whistles.\n\nBuild your own CORS proxy with Cloudflare Workers\n\nWith all the intros out of the way, here's how CloudFlare Workers afforded me to prop up a\nCORS proxy in less than half an hour. If you're impatient and just want to take a look at\nthe service in its full glory then head over to the [cors-proxy repo]. GitHub Actions\ndeploys the service automatically to CloudFlare Workers every time a change is pushed to the\nmain branch.\n\nInstalling the prerequisites\n\nAssuming you have node installed on your system, you can fetch the [wrangler CLI] with the\nfollowing command:\n\nThis will allow us to develop and test the service locally.\n\nBootstrapping the service\n\nCreate a new directory where you want to develop your service and bring it under source\ncontrol. Now, run:\n\nThe CLI will guide you through the entire bootstrapping process interactively. You'll have\nto create a Cloudflare account (if you don't have one already) and log into the dashboard.\nThen it'll prompt you to deploy your first hello-world API endpoint that you can\nimmediately start to play with without doing anything else. Being able to see the serverless\nfunction in action within like 5 minutes gave me a huge dopamine boost that AWS Lambda never\ncould. You can see the interactive bootstrapping section here:\n\n<details>\n<summary><strong>Complete CLI output...</strong></strong></summary>\n\n</details>\n\nRunning the interactive session will create the following directory structure:\n\nDeveloping the CORS proxy\n\nWe'll write our proxy server in src/worker.js file. Copy the following JS snippet and\npaste it to the file:\n\nThe first section of the code deals with extracting relevant information from the incoming\nrequest. It destructures the method, url, and headers properties from the request\nobject, which represents the client's request.\n\nNext, it extracts the destination URL from the query string. It extracts the URL parameter\nusing the searchParams.get() method. If the destination URL isn't provided, the function\nreturns a Response object with an error message and a status code of 400 (Bad Request).\n\nThe code then checks if the request method is OPTIONS. The OPTIONS method is used in\nCORS [preflight requests] to determine if the actual request is safe to send. If the request\nis an OPTIONS request and contains specific headers indicating a CORS preflight request\n(Origin and Access-Control-Request-Method), the function generates a response with\nappropriate CORS headers. The response headers include Access-Control-Allow-Origin to\nreflect the client's origin, Access-Control-Allow-Methods set to `, allowing any HTTP\nmethod, Access-Control-Allow-Headers based on the requested headers, and\nAccess-Control-Max-Age set to 86400 seconds (one day) to cache the preflight response.\n\nIf the request is not an OPTIONS request or doesn't meet the CORS preflight conditions,\nthe code continues execution. It creates a new Request object named proxyRequest uses\nthe extracted destination URL and sets the method and headers of the original request. The\nOrigin header is removed to prevent CORS restrictions when forwarding the request.\n\nThe subsequent code performs the actual request forwarding. It uses fetch to send the\nproxyRequest to the destination URL. If the fetch is successful, the code proceeds to\nprocess the response. It creates a new Headers object from the response's headers and\nmodifies them to include the necessary CORS headers.\n\nFinally, the function constructs a Response object using the response body, status,\nstatusText, and modified headers. If an error occurs during the fetch operation, the\ncode catches the error and returns a Response object with an error message and a status\ncode of 500 (Internal Server Error).\n\nOnce you've pasted the snippet, you can redeploy the service from your local machine with:\n\nThis will deploy the service immediately:\n\nI've removed my root domain from the above output since I'm using the free version of\nWorkers and don't want people to exhaust my free request quota. Haha, security by obscurity!\nBut once you've deployed your proxy server, you can go to the following URL from your\nbrowser:\n\nThis will send you to the Mozilla website through the deployed function. Now you can use it\njust like the free CORS proxy. Try it out by dropping the following snippet to your browser\nconsole. This is exactly the same as the previous fetch snippet but the only difference is\nthis time, we're using our own proxy server that we control:\n\nDon't forget to replace the <your-deployed-service> URL with your own service. This will\nresult in a successful request. You can also interactively send requests to the destination\nURLs via the Cloudflare Workers dashboard. Go to your Cloudflare dashboard, head over to the\nWorkers section, and select your deployed serverless function:\n\n![Cloudflare Workers dashboard showing CORS proxy code in web editor][image_1]\n\nDeploying the service with GitHub Actions\n\nFor one-off services, wrangler deploy in the local machine works perfectly but I usually\ndon't consider a project fully done until I've automated away the whole process. So, I wrote\na quick GitHub Actions workflow to run the linters and deploy the service automatically when\na new commit is pushed to the main` branch. Here's how it looks:\n\nFor this to work, you'll need to create a [Cloudflare API token] and add it to the [GitHub\nSecrets] of your proxy server's repository. Here's the [complete CI workflow file].\n\n\n\n\n[cloudflare workers]:\n https://workers.cloudflare.com/\n\n[cors]:\n https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS\n\n[high-level introduction to CORS]:\n https://medium.com/bigcommerce-developer-blog/lets-talk-about-cors-84800c726919\n\n[cors.sh]:\n https://cors.sh/\n\n[fantastic article on CORS proxies]:\n https://httptoolkit.com/blog/cors-proxies\n\n[cors-proxy repo]:\n https://github.com/rednafi/cors-proxy\n\n[wrangler CLI]:\n https://developers.cloudflare.com/workers/wrangler/\n\n[preflight requests]:\n https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request\n\n[cloudflare api token]:\n https://developers.cloudflare.com/fundamentals/api/get-started/create-token/\n\n[github secrets]:\n https://docs.github.com/en/actions/security-guides/encrypted-secrets\n\n[complete CI workflow file]:\n https://github.com/rednafi/cors-proxy/blob/main/.github/workflows/ci.yml\n\n[image_1]:\n https://blob.rednafi.com/static/images/cors_proxy_with_cloudflare_workers/img_1.png",
"title": "Building a CORS proxy with Cloudflare Workers"
}