DEV Community

Cover image for โœ‹๐Ÿผ๐Ÿ”ฅ CS Visualized: CORS
Lydia Hallie
Lydia Hallie

Posted on

โœ‹๐Ÿผ๐Ÿ”ฅ CS Visualized: CORS

Itโ€™s every developerโ€™s frustration once in a while to see that big red Access to fetched has been blocked by CORS policy error in your console! ๐Ÿ˜ฌ Although there are some ways to quickly get rid of this error, letโ€™s not take anything for granted today! Instead, letโ€™s see what CORS is actually doing, and why itโ€™s actually our friend ๐Ÿ‘๐Ÿผ

โ—๏ธ In this blog post I wonโ€™t explain HTTP basics. In case youโ€™d like to know more about HTTP requests and responses, I wrote a small blog post about it a while ago though ๐Ÿ™‚ In my examples I use HTTP/1.1 instead of HTTP/2, this doesnโ€™t affect CORS.

S H O R T C U T S
โœ‹๐Ÿผ Same-Origin Policy
๐Ÿ”ฅ Client-side CORS
๐Ÿ’ป Server-side CORS
๐Ÿš€ Preflight Requests
๐Ÿช Credentials

On the frontend, we often want to display data that's located elsewhere! Before we can display this data, the browser first has to make a request to a server in order to fetch that data! The client sends an HTTP request with all the information that the server needs in order to send that data back to the client ๐Ÿ™‚

Letโ€™s say weโ€™re trying to fetch some user information on our www.mywebsite.com website from a server thatโ€™s located at api.website.com!

Alt Text

Perfect! ๐Ÿ˜ƒ We just sent an HTTP request to the server, which then responded with the JSON data we asked for.

Let's try the exact same request but from another domain. Instead of making the request from www.mywebsite.com, weโ€™re now making the request from a website located at www.anotherdomain.com.

Alt Text

Wait, what? We sent the exact same request, but this time the browser shows us a weird error?

We just saw CORS in action! ๐Ÿ’ช๐Ÿผ Letโ€™s see why this error occurred, and what it exactly means.


โœ‹๐Ÿผ Same-Origin Policy

The web enforces something called the same-origin policy. By default, we can only access resources that are located at the same origin as the origin of our request! ๐Ÿ’ช๐Ÿผ It's totally okay to load an image that's located at https://mywebsite.com/image1.png, for example.

A resource is cross-origin when it's located at a different (sub)domain, protocol, or port!

image3

Cool, but why does the same-origin policy even exist?

Let's say that the same-origin policy didn't exist, and you accidentally clicked one of the many virus links your aunt sent you on Facebook. This link redirects you to an "evil website" that has an iframe embedded which loads your bank's website, and successfully logs you in by some set cookies! ๐Ÿ˜ฌ

The developers of this "evil website" made it possible for the website to access this iframe and interact with the DOM contents of your bank's website in order to send money to their account on your behalf!

Alt Text

Yeah... this is a huge security risk! We don't want anyone to just be able to access everything ๐Ÿ˜ง

Luckily, the same-origin policy helps us out here! This policy makes sure that we can only access resources from the same origin.

Alt Text

In this case, the origin www.evilwebsite.com tried to access cross-origin resources from www.bank.com! The same-origin policy blocked this from happening and made sure that the evil website's devs couldn't just access our bank data ๐Ÿฅณ

Okay, so... what does this have to do with CORS?


๐Ÿ”ฅ Client-side CORS

Although the same-origin policy actually only applies to scripts, browsers "extended" this policy for JavaScript requests: by default, we can only access fetched resources from the same origin!

Alt Text

Hmm, but... We often have to access cross-origin resources ๐Ÿค” Maybe our frontend needs to interact with our backend API in order to load the data? In order to allow cross-origin requests safely, the browser uses a mechanism called CORS! ๐Ÿฅณ

CORS stands for Cross-Origin Resource Sharing. Although the browser disallows us from accessing resources that arenโ€™t located at the same origin, we can use CORS to change those security restrictions a bit while still making sure that weโ€™re accessing those resources safely ๐ŸŽ‰

User agents (a browser, for example) can use the CORS mechanism in order to allow cross-origin requests which otherwise would've been blocked, based on the values of certain CORS-specific headers in the HTTP response! โœ…

When a cross-origin request is made, the client automatically adds an extra header to our HTTP request: Origin. The value of the Origin header is the origin where the request came from!

Alt Text

In order for the browser to allow accessing cross-origin resources, it expects certain headers from the server's response, which specify whether this server allows cross-origin requests!


๐Ÿ’ป Server-side CORS

As a server developer, we can make sure that cross-origin requests are allowed by adding extra headers to the HTTP response, which all start with Access-Control-* ๐Ÿ”ฅ Based on the values of these CORS response headers, the browser can now allow certain cross-origin responses which wouldโ€™ve normally been blocked by the same-origin policy!

Although there are several CORS headers we can use, there is one header that the browser needs in order to allow cross-origin resource access: Access-Control-Allow-Origin! ๐Ÿ™‚
The value of this header specifies which origins are allowed to access the resources that they're requesting from the server.

If weโ€™re developing a server that https://mywebsite.com should have access to, we can add the value of that domain to the Access-Control-Allow-Origin header!

Alt Text

Awesome! ๐ŸŽ‰ This header is now added to the response that the server sends back to the client. By adding this header, the same-policy origin will no longer restrict us from receiving resources that were located at the https://api.mywebsite.com origin, if we sent the request from https://mywebsite.com!

Alt Text

The CORS mechanism within the browser checks whether the value of the Access-Control-Allow-Origin header equals the value of the Origin that was sent by the request ๐Ÿคš๐Ÿผ

In this case, the origin of our request is https://www.mywebsite.com, which is listed in the Access-Control-Allow-Origin response header!

Alt Text

Perfect! ๐ŸŽ‰ We were able to receive the cross-origin resources successfully! So what happens when weโ€™re trying to access these resources from an origin thatโ€™s not listed in the Access-Control-Allow-Origin header? ๐Ÿค”

Alt Text

Ahh yeah, CORS throws the notorious error that can be so frustrating at times! But now we actually see that it makes total sense



The 'Access-Control-Allow-Origin' header has a value
 'https://www.mywebsite.com' that is not equal 
to the supplied origin. 


Enter fullscreen mode Exit fullscreen mode

In this case, the supplied origin was https://www.anotherwebsite.com. However, the server didnโ€™t have this supplied origin in the list of allowed origins in the Access-Control-Allow-Origin header! CORS successfully blocked the request, and we cannot access the fetched data in our code ๐Ÿ˜ƒ

CORS also allows us to add the wildcard * as the value for the allowed origins. This means that requests from all origins should have access to the requested resources, so be careful!


Access-Control-Allow-Origin is one of the many CORS headers we can provide. A server developer can extend the server's CORS policies in order to (dis)allow certain requests! ๐Ÿ’ช๐Ÿผ

Another common header is the Access-Control-Allow-Methods header! CORS will only allow cross-origin requests if they were sent with the listed methods.

Alt Text

In this case, only requests with a GET, POST, or PUT method will be allowed! Other methods such as PATCH or DELETE will be blocked โŒ

If you're curious about what the other possible CORS headers are and what they're used for, check out this list.

Speaking of PUT, PATCH, and DELETE requests, CORS actually handles those requests differently! ๐Ÿ™ƒ These "non-simple" requests initiate something called a preflight request!


๐Ÿš€ Preflighted Requests

CORS has two types of requests: a simple request and a preflighted request. Whether a request is simple or preflighted depends on some values within the request (don't worry, you don't have to memorize this lol).

A request is simple when the request is a GET or POST method and doesn't have any custom headers! Any other request, such as requests with a PUT, PATCH, or DELETE method, will be preflighted.

In case youโ€™re just curious about which requirements a request has to meet in order to be a simple request, MDN has a useful list!

Okay sure, but what does "preflighted request" even mean, and why does this happen?


Before the actual request gets sent, the client generates a preflighted request! The preflighted request contains information about the actual request weโ€™re about to in its Access-Control-Request-* headers ๐Ÿ”ฅ

This gives the server information about the actual request that the browser is trying to make: what is the method of the request, what are the additional headers, and so on.

Alt Text

The server receives this preflighted request, and sends an empty HTTP response back with the server's CORS headers! The browser receives the preflight response, which contains no data besides the CORS headers, and checks whether the HTTP request should be allowed! โœ…

Alt Text

If that's the case, the browser sends the actual request to the server, which then responds with the data we asked for!

Alt Text

However, if itโ€™s not the case, CORS will block the preflighted request, and the actual request never gets sent โœ‹๐Ÿผ The preflighted request is a great way to prevent us from accessing or modifying resources on servers that don't have any CORS policies enabled (yet)! Servers are now protected from potentially unwanted cross-origin requests ๐Ÿ˜ƒ

๐Ÿ’ก In order to reduce the number of roundtrips to our server, we can cache the preflighted responses by adding an Access-Control-Max-Age header to our CORS requests! We can cache the preflighted response this way, which the browser can use instead of sending a new preflighted request!


๐Ÿช Credentials

Cookies, authorization headers, and TLS certificates are by default only set on same-origin requests! However, we may want to use these credentials in our cross-origin request. Maybe we want to include cookies on the request that the server can use in order to identify the user!

Although CORS doesn't include credentials by default, we can change this by adding the Access-Control-Allow-Credentials CORS header! ๐ŸŽ‰

If we want to include cookies and other authorization headers to our cross-origin request, we need to set the withCredentials field to true on the request and add the Access-Control-Allow-Credentials header to the response.

Alt Text

All set! We can now include credentials in our cross-origin request ๐Ÿฅณ


Although I think we can all agree that CORS errors can be frustrating at times, it's amazing that it enables us to safely make cross-origin requests in the browser (it should receive a bit more love lol) โœจ

Obviously there is so much more to the same-origin policy and CORS than I was able to cover here in this blog post! Luckily, there are many great resources out there like this one or the W3 spec if you want to read more about it ๐Ÿ’ช๐Ÿผ

And as always, feel free to reach out to me! ๐Ÿ˜Š

โœจ Twitter ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป Instagram ๐Ÿ’ป GitHub ๐Ÿ’ก LinkedIn ๐Ÿ“ท YouTube ๐Ÿ’Œ Email

Top comments (83)

Collapse
 
vaibhavkhulbe profile image
Vaibhav Khulbe

This CS Visualized series is unique and a bomb because of those wonderful diagrammatical GIFs/explanation. I want to thank you for all the hard work you put in your articles.

Collapse
 
lydiahallie profile image
Lydia Hallie

Thank you! ๐Ÿ˜ƒ

Collapse
 
ajiteshsingh profile image
Ajitesh Singh

welcome

Collapse
 
jreinhold profile image
Jeppe Reinhold

This is amazing - great down to earth explanation of how CORS works, but just as important, WHY CORS works. You rock!

If I may add, I think it's missing one of the most important parts of CORS, namely that it is NOT a reliable security measure. As it is an opt-in feature by the browsers, it can easily be bypassed by requesting with curl or Postman, or just by installing a browser extension that disables the policy client-side.
I've seen many software engineers mistakenly saying "oh, we've secured our endpoints with CORS, no unauthorized request can happen" - which is not true. It may prevent the user from being tricked into insecure flows (as you explained), but it will not stop hackers from explicitly trying to make malicious requests.

Collapse
 
kewbish profile image
Emilie Ma

Really clear article - gj!

Collapse
 
yashsway profile image
Yash Kadaru

I love when anything is explained visually. This is so well made! Thank you!

Collapse
 
tommulkins profile image
Tom Mulkins • Edited

Those animations really make it easy to digest the topic.

I might have missed it above but important to note that Access-Control-Allow-Origin allows for a single origin value. Otherwise the server will need some middleware to return the header with the right origin value when more than one origin is allowed.

developer.mozilla.org/en-US/docs/W...

Collapse
 
lydiahallie profile image
Lydia Hallie

Ohhh yes! I made the mistake once by padding an array instead haha. I'll update the post accordingly asap :)

Collapse
 
owfm profile image
owfm

Sorry if I'm missing something... But in your very first example:

Letโ€™s say weโ€™re trying to fetch some user information on our mywebsite.com website from a server thatโ€™s located at api.website.com

And it goes through fine... How come this is not a cross origin request? It's a different domain and subdomain isn't it?

Collapse
 
epic_qi profile image
Epic Lee • Edited

I believe that's a typo. Should both be mywebsite.com, otherwise it's a cross-origin request

Collapse
 
caioiglesias profile image
Caio Cesar Iglesias

You are correct. It's just an intro showing a request that works followed by one that fails. They seem similar, but are being affected by the same-origin policy.

api.website.com has to be returning Access-Control-Allow-Origin: https://www.mywebsite.com since the second example denies the request coming from anotherdomain.com.

Collapse
 
pavelloz profile image
Paweล‚ Kowalski • Edited

Letโ€™s say weโ€™re trying to fetch some user information on our mywebsite.com website from a server thatโ€™s located at api.website.com!
...
Let's try the exact same request but from another domain. Instead of making the request from mywebsite.com, weโ€™re now making the request from a website located at anotherdomain.com.

Either i dont understand the next couple sentences explaining how to tell if request is cross origin, or there is an error here, because it looks to me that both requests have been cross origin ;)

Collapse
 
jeromefitzpatrick profile image
jeromefitzpatrick

@pawel Kowalski

You are correct - either this is an error or maybe api.website.com allows the mywebsite.com origin!

Collapse
 
hanskerkhof profile image
Hans Kerkhof

Great article, thanks! This article explains it so beautifully that I can send to colleagues back-enders who have a glassy look full of incomprehension when i mention I need CORS in the frontend. (and that happens more often than you think ;-) )

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

Does CORS affect cURL or wget? Or, does enforcing CORS help?

Collapse
 
lydiahallie profile image
Lydia Hallie • Edited

Good question! By default, CORS is only enabled in user agents (e.g. a browser). So if I were to send the exact same request using cURL in say the terminal, this request wouldn't automatically contain the Origin header and CORS wouldn't be enabled.

If you want to test whether your server has CORS enabled, you can manually add the Origin header to the request. If the response contains the Access-Control-Allow-Origin header, you know it's working ๐Ÿ˜ƒ

Collapse
 
thatiitgirl profile image
thatIITgirl

WOW! I'm amazed with the clear explanation. Also Lydia, Could you tell me what software you are using for animations. I so want to work on that. :) . Please !!