CORS
Agenda
- Discuss how the web works
- Discuss challenges associated with building web applications
- What makes applications ‘slow’?
- What is a CDN?
- Why/What is CORS?
Learning Goals
- Student understands why CORS was created
- Student can explain why CORS is not very secure
- Student can determine whether two URLs have a matching origin
- Student can set
Access-Control-Allow-Origin
in a NodeJS app
Vocab
- Request
- Response
- HTTP
- Verbs (GET, PUT, POST, PATCH, DELETE)
- Content Delivery Network (CDN)
- Cross Origin Resource Sharing (CORS)
Lesson
The Problem
Your web application serves up several kinds of content:
- HTML
- Images
- JavaScript
Over the last decade server CPU time has become cheap, disk space is near free, but the data you transfer still costs a good bit of money.
So, to save $$$, I have my website loading jQuery fetching jQuery off your server. I’m also using some images that you host. Thanks for the free bandwidth!
SECURITY! THERE’S BEEN A BANDWIDTH ROBBERY!
Because HTTP is a stateless protocol, there’s no (easy) way to know if the asset being requested is requested by a user of our website or some other website. For example, in a normal scenario:
- User A requests the HTML from Wonderful Wanda’s site
- User A’s browser reads the HTML and requests the images and JavaScripts from Wonderful Wanda
- User A’s browser displays the HTML, injects the images, and executes the JavaScript
But in Step 2 the server doesn’t know that User A is the same person who previously requested the HTML. That means this could happen:
- User B requests the HTML from Evil Eve’s website
- User B’s browser reads Eve’s HTML, but it tells the browser to fetch the images and JavaScripts from Wanda’s website
- User B’s browser gets the assets and displays them, all the while using up Wanda’s bandwidth
This could potentially cause extra load on Wanda’s website, cost her extra money to pay for the bandwidth, or other problems.
Fake Security with CORS
Up-to-date browsers implement a technique to hinder the above stealing called CORS: Cross-Origin Resource Sharing
.
How does it work? The headers
in a typical HTTP response might look like this:
GET /v1/data HTTP/1.1
Host: api.example.com
Origin: http://www.example.com
HTTP/1.1 200 Ok
Content-Type: application/json
Content-Length: 4365
Following the CORS standard means adding one additional line like this:
GET /v1/data HTTP/1.1
Host: api.example.com
Origin: http://www.example.com
HTTP/1.1 200 Ok
Content-Type: application/json
Content-Length: 4365
Access-Control-Allow-Origin: http://www.example.com
Adding It…To What?
This HTTP header, Access-Control-Allow-Origin
, can be added when serving up any file/asset on the server. Let’s return to the original scenario:
- User A requests the HTML from Wonderful Wanda’s site
- User A’s browser reads the HTML and requests the images and JavaScripts from Wanda
- The images and JavaScripts are served with
Access-Control-Allow-Origin
set to the domain of Wanda’s website - User A’s browser displays the HTML, sees that the
ACAO
address matches the origin of the original HTML, then injects the images, and executes the JavaScript
Everything works out as expected. But when User B hit’s up Evil Eve…
- User B requests the HTML from Evil Eve’s website
- User B’s browser reads Eve’s HTML, but it tells the browser to fetch the images and JavaScripts from Wanda’s website
- The images and JavaScripts are served with
Access-Control-Allow-Origin
set to the domain of Wanda’s website - User B’s browser gets the assets, see’s there’s a mismatch between the origin of Eve’s HTML and Wanda’s assets’
ACAO
, and it refuses to embed the images or execute the JavaScript
This is only achieving the slightest bit of “security” because it’s up to User B’s browser to decide, in step 4, whether or not to respect the CORS conflict and cause problems.
In fact, if User B is in on the plan, they could just disable CORS on their browser with a Chrome extension.
What does it all mean?
More than likely, CORS will be a pain in your development and never really save you any money or bandwidth. Sorry.
Are you serious? All this is about a technology I don’t care about?
Yes, and no, and yes. Yes you don’t want to care. But you’ll have to because you’ll run into problems like this:
- You are running your own front-end code on
http://localhost:3000/index.html
- You are running your own API on
http://localhost:3001/api/v1/cookies
Your browser rendering the index.html
will freak out about loading data from the API because they have different “origins”.
What is an Origin?
The “origin” of a resource is the protocol, port, and host of where a resource is stored. Two pages are said to have the same origin if those details match for both pages.
When your HTML loads from http://localhost:3000/index.html
the following other requests could happen:
URL | ORIGIN MATCH | REASON |
---|---|---|
http://localhost:3000/other.html | YES | |
https://localhost:3000/other.html | NO | Different Protocol (http vs https ) |
http://example.com | NO | Different Host |
http://localhost:3001/other.html | NO | Different Port |
So http://localhost:3000/index.html
and http://localhost:3001/api/v1/cookies
would mismatch because of the different port.
TELL ME WHAT TO DO, MAN!
On the API side, let’s say you’re running an ExpressJS application. You want to set the headers so that the data from that API is allowed to be fetched/displayed from any origin. You’d add this code to your app:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
//or
app.use(cors({
allowedOrigins: ['localhost:8000']
}));
The data/assets served by that API will have Access-Control-Allow-Origin: *
set so even CORS-enabled browsers will let them be displayed on any page.