Introduction
CORS protocol or Cross-Origin Resource Sharing is a layer on top of HTTP that allows AJAX and Fetch requests to request data from different origins such as an IP or domain.[1] Technically speaking, the CORS protocol is a part of the standard that web browsers (Chrome, IE, Safari) use to implement javascript.
The purpose of CORS is to provide a layer of security in the web browser to protect any personal information that may be sent over HTTP. In this article, we’ll take a pragmatic approach to CORS by looking at how to make APIs CORS enabled. In addition, we’ll examine CORS with front-end and back-end development perspectives.
Basics of CORS
The simplest description of CORS can be described in the diagram above. CORS is the capacity to share resources via different origins. An example is how the internet works. Our web browsers request URLs from different origins (or servers). Front-end web developers access servers (ie cross-origin resources) by accessing REST APIs using the javascript Fetch API or the popular HTTP library Axios.
When we say origin we are talking about the domain, IP address, server, or location of the script on the internet making the request. (domain, IP address, and server are synonymous). For example, developers usually make HTTP requests using the Fetch API from localhost while developing applications.
When HTTP requests are made in javascript, the web browser performs a CORS check before actually making the request. This check is called a “preflight request”. The purpose of the preflight request to find out if the request is capable of accessing the resource. If this preflight request fails, the original request is completely canceled. This is why when you create an API without CORS enabled, it will work on localhost but not when moved to a production domain.
For example, let’s say I’m working on an application to check the stock prices. I have the following javascript code:
const sharePrices = {}
fetch("https://stockpricesapi.com/prices/amazon,google,ibm", {
method: "POST",
body: JSON.stringify({apiKey: "my0p0k0y"})
})
.then(res => res.json())
.then(data => {
sharePrices.amazon = data.amazon.price
sharePrices.google = data.google.price
sharePrices.ibm = data.ibm.price
})
Let’s pretend that the above Fetch requests access the stockpricesapi.com to retrieve the current stock prices of Amazon, Google and IBM. When we make this request from localhost, where we typically develop code, the request will be “checked” with a preflight request to make sure that the request is allowed from localhost. So, while my code only makes one fetch request, the browser makes two requests.
The first request the browser makes is an OPTIONS request to make sure the request can be made. If this request fails, the browser will cancel the whole request. This is important for security reasons that we’ll look at next.
Why do web browsers use CORS and “preflight” checks?
So why do browser vendors perform this preflight check when making requests from javascript? Well, the short answer is that it is part of the Fetch API standard which greatly influences how javascript is implemented. However, the primary reason browser vendors implement CORS is for security. By making cross-origin access an “opt-in” decision, rather automatically allow requests to any website from javascript, browsers are more secure by default. Making the cross-origin access open by default could create many more opportunities for malicious scripts to trick people into giving up personal information.
With CORS and preflight checks, the internet is more secure by default.
How to enable CORS in your API?
Enabling CORS for your API is not difficult. In this section, we’ll see how to allow access to your API from a single domain and any domain. We’ll see how this implementation works in ExpressJS and via an AWS Lambda function served over AWS API Gateway.
If you want to allow access to your API from different origins, you’ll need to make sure to have your API endpoint respond with special headers that tell the web browser that it is ok to access the resource. There are two primary headers you must include with the API response to allow your API to be accessed from different origins.
- Access-Control-Allow-Origin – This header responds to requests with the origins or domains that are allowed to access the API.
- Access-Control-Allow-Credentials – The header tells the request whether it can use the same credentials for subsequent requests.
Now let’s look at how to enable CORS for your API in Express/Node and Python/Aws Lambda.
Enable CORS for your Express JS API
Express is the most popular web framework for Node. If you are making an API, you’ll probably be using Express. Express is a great framework for creating REST APIs.
One way to enable CORS for your Express API is to create a lightweight middle-ware to attach headers to each response from all endpoints.
The following code comes from CORS on ExpressJS:
// Middleware
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "YOUR-DOMAIN.TLD"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// API endpoint 1
app.get('/', function(req, res, next) {
// Handle the get for this route
});
// API endpoint 2
app.post('/', function(req, res, next) {
// Handle the post for this route
});
In the code above, the first function appends CORS headers to each request. So, if you make a javascript request to one of the endpoints the correct CORS headers are already there and the preflight check with pass.
The CORS header Access-Control-Allow-Origin allows for the Fetch request to be made from the domain YOUR-DOMAIN.TLD. You could substitute this domain for your own custom domain. You could also put a “*” and that would allow access from any origin such as localhost.
Example of allowing access to your API from any origin:
app.get('/', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
});
Rather than using the code above, its best to use a more mature CORS middleware for Express found at https://github.com/expressjs/cors. The code is much cleaner and the middleware has more features. The following code will automatically add the necessary CORS headers to all responses from your API.
Example using Exressjs/Cors:
var cors = require('cors')
var app = express()
app.use(cors())
Enable CORS for your AWS Lambda API (Python)
The same principle applies when allowing cross-origin access to an API on AWS Lambda being served from AWS API Gateway. For Python, you need to return a response with the appropriate response headers.
return {
"statusCode": 200,
"headers": {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': True,
},
"body": "This is response text."
}
The response above from a Lambda function in AWS would be available to any origin on the internet including localhost.
Conclusion
CORS can be a headache at first, but the additional security it provides is important when creating or accessing APIs. CORS and preflight checks make the web a little bit safer bit default. By making sure your APIs are only accessed from domains you specify, you keep your APIs safe from bot attacks or malicious requests.
Thanks for reading!
Further Reading
- https://enable-cors.org/
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control - https://fetch.spec.whatwg.org/#http-cors-protocol
References
- “Fetch”. fetch.spec.whatwg.org. Retrieved 2019-09-08.
Leave a Reply