In the old days of the internet, websites typically loaded all of their assets from their own server. The HTML and Javascript were served up from the same domain name. Those were simple times.
Modern websites almost always load data from other servers, however. This is great because it enables a significant amount of sharing across the internet. For things like images, this can be nice. You can, for example, display an image from someone else's domain, like this:
![Kitten](https://www.rd.com/wp-content/uploads/2019/04/shutterstock_1013848126.jpg)
Clearly for things like kitten images, this is not an issue. The image presents no serious security risks. In fact, the only risk might be that I take credit for someone else's incredible work photographing kittens... or perhaps that the other domain moves the kitten image location and breaks my link. But not a big deal here.
However, by default web browser enable making not only GET
requests, but POST
and PUT
- and DELETE
- requests to other domains. Clearly, this is dangerous. If you create a cool website that has the ability to edit customer records, you would hopefully add a security layer that provides some form of authentication in order to get and manipulate your customer data. So far so good.
But what if someone else decides to make a malicious website that pretends to be your website and forwards the login request (with username and password) to your domain? In this case, they could steal your users data and even edit or delete it. And the user might not notice the difference!
maliciousdomain.com POST to mynicedomain.com/login
Clearly mynicedomain.com
needs to be smart enough to know when the login request is coming from... itself. In other, words, there should be a way for mynicedomain.com
to reject all requests from maliciousdomain.com
.
This is where CORS comes in. Cross-origin resource sharing is web standard that all modern browsers adhere to that ensures that with every request that is sent, the headers include the domain that is making the request. The browser is designed to help ensure that some other website cannot pretend to be yours. It always sends the header indicating the domain that is making the request.
This means that is up to your web server code to protect itself from malicious domains. As a result, by default most web server software comes with baked in checks to only allow requests from... itself. Configurations vary wildly, but the same principles apply.
Setting up CORS
Typically, your web server will have a plugin - or a built in feature - that allows you to create a black- or white-list of allowed domains. It might look something like this (taken from the Node.js Express CORS setup):
var express = require('express')
var cors = require('cors')
var app = express()
var corsOptions = {
origin: 'http://example.com',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only example.com.'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
In this case, we see that this enables one origin domain to make requests: http://www.example.com
.
Your web server (or middle-man like NGinx or Apache) will have varying settings to help with this. As a rule, if possible, I like to do the checking in my native web server code (like Javascript) because it makes it a little bit easier to find in the configuration, but this is entirely up to you. You might be able to configure it in the Nginx or Apache layer as well. You can find a large collection of CORS configuration examples here.
CORS on Localhost
When developing, a common problem is that you might also want to enable CORS in localhost
. For example, if your website is served from a different port than the port for your API. In this case, you may need to add more than one domain and port to your CORS filtering.
Another problem might be that you know the person developing the API and they do not have time yet to enable CORS for you. In this tight crunch, there is a workaround. You can find some plugins and settings for Chrome and Firefox that allow the browser to bypass the CORS restrictions. Keep in mind that this opens a major security vulnerability. This option should only be used for the website you are testing and when you have a solid grasp on what you are doing.
Why Can't I Just Turn CORS off all the time?
CORS is not just about protecting mynicedomain.com
from malicousdomain.com
. It also keeps malicious libraries from hijacking your own website and doing dangerous things. For example, you might install an npm package that - without your consent - tries to send or pull data from another domain. The scope of these intrusions is beyond this post, but keep in mind that CORS is there to protect you, even though it might be annoying during development.
Conclusion
While at first CORS might be frustrating, it is an essential part of the modern web. With just a simple understanding of how it works, you can quickly find configuration to make your development both more secure and able to communicate across domains.