This article is a step by step tutorial on how to bypass CORS restrictions and integrate external resources in your website by implementing a simple proxy using a combination of two AWS services: API Gateway and Lambda. We will be creating a proxy against a real world example - for educational purposes only- . This is intended to expose the problem and to help people grasp the implementation steps. To follow along, a basic AWS terminology knowledge with a valid account, nodejs and -of course- CORS are highly recommended.
Definition & Problem exposing
Most of the time, you have been developing on your local machine, exposing a local web server at http://localhost:8080. Your website not working properly so you open the google chrome developer console to see the dreaded CORS error:
This showcase a typical CORS alert and to lay it down to you, it is a browser security mechanism that automatically blocks solicitation of resources hosted in a different origins from which the original page was loaded, and authorizes it only when the requested resource are specifically whitelisted. This allow/bloc behavior is defined at the web server level. To read more, check the official documentation here.
This works pretty well, but what happens when we try to use the same API endpoint in our website, hosted at https://www.azaytek.com? Yes, you guessed that right: CORS will prevent us from doing so and we are stuck 😣
To solve this, we can pretty sure build our own proxy server using any tech stack we choose and then host it somewhere. The main disadvantage of this is that we need a 24/7 running server, even when it's yawning and not receiving requests. Here comes the interesting pay-as-you-go AWS cloud payment model: we are charged only for what we're really using, keeping in mind that the AWS API Gateway + AWS Lambda combination for our use case is pretty cheap, almost ≈3.5$ for 1 millions requests: Lambda pricing / API Gateway pricing without forgetting that we are using AWS performant, highly available and scalable infrastructure.
To implement our solution using AWS services, we will use AWS API Gateway to expose a public HTTP URL. This URL, when invoked, will trigger an AWS Lambda function that will do the concrete request ( I will be using nodejs & the awesome fetch package but you can use any technology as long as it is supported by Lambda). Once it gets the original response , it passes it to API Gateway to return it back to the client. Here is the overall architecture:
AWS Lambda setup
We will create a lambda function using nodejs and the fetch package. The key point here is that the fetch package is not included by default and we need to upload the full project as a zip file ( index.js, package.json and the node_modules/ folder). Keep in mind that you can implement the get request in any technology supported by AWS Lambda.
Steps to deploy the code:
To create the function, kindly follow the steps described here: https://docs.aws.amazon.com/lambda/latest/dg/getting-started-create-function.html. Once created, you can go to source code's section and write your code in the online editor or, as in my case, simply upload the project as a zip file
At this level, we have an AWS Lambda function that's working. To test this, We go to the "Test" tab and we need to pass it a JSON object that reflects the JSON object that our function would receive if it was triggered by AWS API Gateway: the test input should look like this
Once done, we click "Call" and we will see the result of our function execution and whether it was successful or not. Looking at the details we will find more information, here is a sample: (We can see that our function did the GET https://psl.service-public.fr/services/public/rest/communeOrCp/ by passing it a terms parameter equals to "paris", as set in test input data)
👍Bonus Tip: to get more insight on the data structure the lambda function would receive when triggered via API Gateway, you can take a look at https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format.
Now, our Lambda function is setup and works when we test it from the AWS dashboard or invocked via API, but how could we make it publicly available to the world (internet)? Yes, you guessed that right: AWS API Gateway is the solution.
API Gateway setup
To give you a brief and concise introduction of this AWS service, here is a quoted definition from the official documentation:
Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. ... Using API Gateway, you can create RESTful APIs and WebSocket APIs that enable real-time two-way communication applications.
For plain English definition of AWS API Gateway - alongside others AWS services you can check this simple, yet awesome article.
Create the Gateway trigger
In order to create and deploy our public HTTP endpoint: we will create a new API Gateway API that will trigger our previously deployed Lambda function. This API endpoint will receive our URL parameters, passes them through to our lambda function that will execute the external request and get the response back to the client.
To this, simply, on our Lambda function page, in the "Function overview" section at the top of the page, click on "Add a trigger" and then select API Gateway.
This will prompt you to create a new API. Choose that option, and select HTTP API.
Give it a name and continue (At this stage you can enable CORS in the additional parameters section, but to keep it simple, personally I recommend to do it later). For this tutorial only, I choose to make it an open API by choosing "open" in security field.
Once done, click "Add" at the bottom right. This will create a new gateway with a randomly generated URL and will set it as a trigger of our Lambda function. Note that this is the base URL, so if you want to call this to trigger your function you have to concatenate it to the name of your trigger you defined. Example:
📝 With a base URL [ https://alkj54au.randomely-generated-url.com/default], with a Lambda trigger named [ my-trigger-api ], the full URL becomes [ https://alkj54au.randomely-generated-url.com/default/my-trigger-api ]. Note that this is the name of the trigger, not the name of the Gateway.
If you are lazy like me, you can get the public endpoint when switching to the Lambda function configuration tab:
Now go to API Gateway dashboard page and you will find the API you've just created, with the name you gave it to
Click on your Gateway and you will see our Gateway dashboard page, and its configuration options
❤️🔥 Dada 😎! We have a public URL that triggers our Lambda function. Call it from your browser and you should get a response. Now, we will passe our Gateway URL parameters to our Lambda function. Simple, keep reading.
➕ If you want to configure CORS to your endpoint, security mechanism or throttling, deployment environments etc. you can (and should) do it in the gateway dashboard page. This is beyond the scope of this paper
Pass the URL parameters to Lambda
[ This is was mentioned in section, but I would like to reformulate since we already draw the 360° picture ]
Lambda function when invoked, gets the event data source as a json format. To get a grasp of data that gets passed to Lambda using different types of trigger you can this resource. We interests us here is the API Gateway data source:
The HTTP request that was sent to our Gateway is proxied to or Lambda function in the above format, URL parameters are present in the queryStringParameters. So for example, when calling our gateway with https://www.mygatewayurl.com/my-trigger-name?a=1&b=2 you will have a JSON "queryStringParameters" as:
From there you are free to use this data to execute the external, original HTTP request and get back the response. Done. Long but worth it.
This is the end of this tutorial. We have seen how to create a HTTP proxy by combining AWS Lambda and API Gateway in order to bypass CORS restriction on publicly exposed resources in the internet. I hope this was helpful and I would like to insist that we are living in such a fascinating cloud and internet era where problems and solutions are everywhere! Everything is possible and the sky is the limit! As always, your feedback, questions or suggestion is highly welcome 😃