Before we actually start learning about CORS, let's learn some jargon that we need in order to have clear understanding of CORS.
- URL
URL stands for uniform resource locater, in short it is a unique address for the content in the web so that browsers can load them.
Below is the image which tells us about various parts about the URL, have a look at it!
- Site According to MDN docs: Informally, a site is a website, which is a collection of web pages, served from the same domain, and maintained by a single organization.
Now let's see various examples regarding sites:
-
https://developer.mozilla.org/en-US/docs/
andhttps://support.mozilla.org/en-US/
both are considered as same site because the registrable domain (mozzilla.org) is same. -
https://example.com:8080
andhttps://example.com
are same site because port is not relevant when we check for site. https://developer.mozilla.org/en-US/docs/
andhttps://example.com
are not considered as same site.Origin
An origin for a website is defined by its scheme(protocol) + host(domain) + port.
So if we say two websites have the same origin that means they both have same scheme, port and domain.
CORS (cross origin resource sharing)
Back in the day due to some security reasons browsers were operated on same origin policy, it means a client can only send request for resource(usually a server) if and only if both of them have same origin, but eventually people realized it has many limitations. Sometimes we need to depend on external resources for data like images..etc, but due to SOP(single origin policy) it was not possible, so then they added cross origin resource sharing policy on top of single origin policy, that is we have to set up the cors policy.
Some gotchas about CORS:
- Who is getting benefited from CORS
- Many of the people think CORS is making our API secure, the answer is NO, both CORS policy or SOP aren't meant to secure our API.
- Let's say there is a route "/a" with an origin www.a.com and you have a client with origin "www.b.com". If you hit the "/a" using fetch in client, the response of the request will be blocked and CORS error will be logged to the browser console, but the route logic is executed in the backend, and remember there are some exceptions cases where backend route logic also will not be executed in a CORS request. One of such case is preflight. It may sound very confusing and hard to believe but it's true. Apart from exception cases the backend route logic is executed in CORS requests, it's just the browser blocking the client from viewing the response.
- Is CORS helps in preventing CRSF(Cross-Site Request Forgery) attacks
- No CORS will not help in preventing the CSRF attacks, the whole motto of CORS is to enable cross resource sharing, but not securing our API.
Let's see working of CORS in a higher level. Usually how CORS work is, when the client sends a request to a different origin resource(server), usually it will give you error because by default browser operated on SOP. Let's say now your client origin is "www.a.com" and your server origin is "www.b.com" and now you setup CORS on your server for client origin("www.a.com"). Now if you send a request to server from client, in the headers of the response for that request, the server attaches a header known as Access-Control-Allow-Origin
and sets its value to the client origin that is Access-Control-Allow-Origin : www.a.com
so now the browser checks this particular header and prevents blocking the response. This is a high level overview of working, but if you want complete picture of working I attached all the resources at the end of the blog, have fun😁.
Let's see CORS in action 😉
index.html
client
* {
padding: 0;
margin: 0;
}
body {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
gap: 30px;
}
button {
padding: 10px;
}
send a request to test1
async function test1() {
let response = await fetch("http://localhost:7070/test1");
console.log("/test1", response);
}
Enter fullscreen mode
Exit fullscreen mode
server.js
Backend file
let express = require("express");
let cors = require("cors");
let app = express();
app.get("/test1", (req, res) => {
console.log("route logic for /test1 executed");
res.send("response from /test1");
});
app.listen("7070", () => {
console.log("server started at port:7070");
});
Enter fullscreen mode
Exit fullscreen mode
Now I didn't setup any CORS and my index.html origin is http://127.0.0.1:5500 and my server.js origin is http://localhost:7070, so because both client and server has different origins, if we click on 'send a request to test1' in frontend, the request will have a CORS error. Error in console:But if you go to the network tab and check the request status code carefully:That's the point, the request will execute the route logic, it's just the browser blocks the response from viewing and also if we go to our server.js console you will see route logic for /test1 executed. The points here I am trying to make you understand are, I want you to show how the CORS error looks and make you understand the SOP or CORS policy is not protecting the API, that's not its purpose.So now let's see how we setup CORS, in express we setup the CORS by setting a CORS middleware:
let express = require("express");
let cors = require("cors");
let app = express();
app.use(cors({ origin: "http://127.0.0.1:5500" }));
app.get("/test1", (req, res) => {
console.log("route logic for /test1 executed");
res.send("response from /test1");
});
app.listen("7070", () => {
console.log("server started at port:7070");
});
Enter fullscreen mode
Exit fullscreen mode
And now after setting this you see the CORS error will be gone and you will smoothly get the response for your request. Now let's check what we learnt about how the CORS actually works:The response headers without CORS:The response headers with CORS setup:There we go, when CORS was setup properly we got the special headers that we learnt about 🥳🥳🥳🥳Everything is good so far. Now, let's talk about the exception case we discussed earlier. There's a special behavior that occurs when CORS isn't properly configured — in some scenarios, the route logic doesn't get executed at all.One such case is when making a cross-origin request using the POST method with the Content-Type set to application/json. In this situation, the browser sends a preflight request (an OPTIONS request) before the actual request. This preflight request includes details like the HTTP method and headers that will be used.If the preflight request fails — for example, if the server doesn't respond with the proper CORS headers — then the browser will not send the actual request. As a result, the route handler for that endpoint won't be triggered at all.You can find more details about these cases in the resources at the end.Now let's see that in action:
index.html
client
send a request to test1
send a request to test2
async function test1() {
let response = await fetch("http://localhost:7070/test1");
console.log("/test1", response);
}
async function test2() {
try {
let response = await fetch("http://localhost:7070/test2", {
method: "POST",
body: JSON.stringify({
dummy: "i am dummy body data",
}),
headers: {
"Content-Type": "application/json",
},
});
console.log("/test2", response);
} catch (error) {
console.log("error from /test2", error);
}
}
Enter fullscreen mode
Exit fullscreen mode
server.js
let express = require("express");
let cors = require("cors");
let app = express();
app.get("/test1", (req, res) => {
console.log("route logic for /test1 executed");
res.send("response from /test1");
});
app.post("/test2", (req, res) => {
console.log("route logic for /test2 executed");
res.send("route logic for /test2 executed");
});
app.listen("7070", () => {
console.log("server started at port:7070");
});
Enter fullscreen mode
Exit fullscreen mode
Let's consider the above code and let's try to hit the /test2 endpoint. When you do that and open your network tab you see something called preflight request, it's the thing that we are talking about. If this is successful then only the actual request will be sent by the browser otherwise it won't happen.Try the same thing after configuring that with CORS, still you see that preflight request but this time it will hit the server.
Thank you for reading, hope it made your learning process smooth. If it really helped you shoot me an email😁
Resources that helped me to learn about this topic:
https://developer.mozilla.org/en-US/docs/Learn_web_development/Howto/Web_mechanics/What_is_a_URL
https://developer.mozilla.org/en-US/docs/Glossary/Site
https://developer.mozilla.org/en-US/docs/Glossary/Origin
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
https://www.youtube.com/watch?v=EdJ6RJ3uGbo
https://maddevs.io/blog/web-security-an-overview-of-sop-cors-and-csrf/