CORS is confusing to understand because the kind of attack it protects against is confusing.
CORS does not protect endpoints against malicious clients, since you can always just make the same request outside of a browser. And it doesn't protect any site from making or receiving cross-site requests, since CORS can always be disabled on the server side.
CORS protect against the scenario where a malicious site tricks an unmodified browser to make a cross-site request to a legitimate site. If the user has an authentication cookie for the legitimate site, the cookies will be sent along with the request. So the malicious site can perform transactions in the legitimate site on behalf of the user, despite not having direct access to the authentication cookie.
CORS is further complicated because certain forms of cross-site requests have always been allowed by browsers, and therefore must remain enabled by default for backwards compatibility. GET requests to separate sites is allowed, since this has always been allowed e.g. to embed images from other domains. POST requests are allowed with the caveat that you can't inspect the result, because you have always been able to initiate a post to a different site from a html form.
I know the official line is that CORS “protects” the relying party (the website that initiates requests) but in practice I have found it to be quite the opposite.
From having to deal with CORS over the years, to me is just a tool to protect one thing… the intellectual property of whatever site we are loading resources from.
For example, images arrive “tainted” and you can’t get their pixels on a canvas no matter how hard you try. You can’t generate a “screenshot” you can print, and so forth.
If this was just about protecting the relying party website, that website would have a way to get around this “protection” to get something done.
Sure, people can try to get at the intellectual property directly, but they’d have to have an authenticated account first. If they do it on the site where it’s hosted, they are subject to its rules. If they are coming from some random site, eg loading it in bulk, the resulting info they get can’t be analyzed by the Javascript.
Yes CORS is just a relaxation of SOP, which makes it impossible to get sensitive data that would have otherwise been revealed to the relying party. Again — SOP is protecting the user’s data loaded from the ORIGIN. Its main job is’t “protecting the receiving site”, that’s a pretty disingenuous characterization.
> CORS protect against the scenario where a malicious site tricks an unmodified browser to make a cross-site request to a legitimate site. If the user has an authentication cookie for the legitimate site, the cookies will be sent along with the request. So the malicious site can perform transactions in the legitimate site on behalf of the user, despite not having direct access to the authentication cookie.
> CORS is an implementation in the browser and is designed to protect the user from malicious applications by ensuring that the resource in the browser is only allowed to access specific endpoints.
> This browser implementation can be bypassed at any time. First, it is up to the browser itself: if CORS is not integrated, or not integrated cleanly, then it will not work.
> An attacker can access the API key via the source code of the web app and use it, for example, via a cURL request to directly access the API resources of the backend. With cURL, no CORS takes effect, so the attacker has direct access with the full rights of the user.
All of this is completely wrong.
CORS does not protect anything from anyone. The same-origin policy stops code from one site reading resources from another site. CORS selectively removes that protection – it decreases security.
If CORS is not integrated, the same-origin policy is in full effect and code from one site cannot read resources from your site. It starts off as secure, and stays secure.
If you use cURL, then the same-origin policy doesn’t apply, and you can of course read any resource.
The same-origin policy is not a generic access barrier, and shouldn’t be used as such. It stops one site from abusing the user’s authenticated state with other sites, and that’s it.
> CORS does not protect anything from anyone. The same-origin policy stops code from one site reading resources from another site. CORS selectively removes that protection – it decreases security.
This comment comes off as either disingenuous or needlessly contrarian, and in the process tries to make points that fall somewhere between completely wrong and miopic.
Your personal assertion that CORS somehow decreases security is based on the patently false assertion that at any point in time requests only came from the same origin. Not only was this never the case, this ignores the fact that with the popularity of REST and SPAs and microservices and API-as-a-service, the norm was since switched to having browsers make requests to anything other than the same origin.
So, without CORS, you have a all-or-nothing security model, where the needle would always pend to the "nothing" side. With CORS, that needle can point "all that matters", which is pretty close to the optimal same-site solution. What CORS does is allow developers to abstract away the definition of "same origin" to mean "the origins that I explicitly allow".
You simply cannot claim that allowing only requests to the origins you explicitly allow is an erosion of a security model. That makes no sense. It made no sense when implicitly that list was only comprised of your own domain, it makes no sense now when that list includes reputable APIs that you pay to use.
From experience, I can tell you that many people simply refer to this entire domain as CORS despite that S standing for Sharing. The Same Origin Policy is treated verbally more like the default state of CORS in some circles.
It is very confusing and I’m not entirely sure how it ended up like that.
It is more complex than that, because certain kinds of cross-site requests has always been allowed. GET and POST requests are allowed, but PUT and DELETE is not. For POST requests you can send the request but not access the result. So CORS can be used to increase protection, by disabling cross-site POST requests, but it can also be used to decrease protection for other requests.
I agree, the author doesn't seem to grasp the fact that CORS always waters down security. That is, CORS is the way of security weakening. Although the author correctly notes that only browsers respect CORS, there is no mention that this happens because CORS relaxes security built into browsers.
Ah, CORS. One of those rare topics to fall under the category of: "I have no idea how it works. I run into an issue with it. I spend all day researching, reading documentation, blog posts, etc, explaining it. I finally come to some understanding. Three weeks later, start the process again."
To my weak-minded brain, the sticking point always comes down to: CORS/SOP is a policy enforced by the browser, correct? The client. The endpoint tells the client what origins should and should not be allowed to make the request. It is up to the client (browser) to decide whether to enforce this policy or not. Is that at least somewhat correct?
Yes. The other confusing thing about CORS is that CORS is not a protection against anything, but rather a way to UN-protect (in a restricted fashion) the browser's "same origin policy". Same Origin Policy is the protection, and CORS is the way to allow the browser to poke a hole through that in a limited fashion (only for the domains that the original site you visited say it should allow).
In addition to what jordanlev said: the server tells the client that the origin the client sent is allowed. It's basically a boolean but for some reason the access-control-allow-origin header specifies an origin URL.
Having read the original article (https://designkojo.com/post-drupal-using-jsonapi-vuejs-front...), it’s quite clear that the author doesn’t know enough about what he’s talking about to write these kinds of posts. I wouldn’t usually say this, but on security-critical topics like this, winging it just isn’t good enough. People will be misled by this article.
Exactly. I was gonna give it the benefit of "maybe the original is JUST running the Vue.js App locally, never published anywhere, so adding the API key in the environment is fine" but no, reading the original it's clear that the author just has no idea what they are doing.
That’s oversimplified to the point of being extremely misleading. Under SOP, the way you loaded third-party data (not code) was JSONP, where (oops!) you’re actually loading third-party code but you just cross your fingers and hope the third party doesn’t deliver you any nefarious code.
The comments in this thread accurately reflect my own personal journey with CORS. Like waves on the shore, my confidence in my understanding of the protocol ebbs and flows with the orbit of the Moon.
Getting CORS right involves a couple days of forgetting what I think I know, closely reading how it works (again), writing up a custom middleware for whatever web framework I’m using that time because the OOTB middlewares always have subtle bugs (usually with the Origin header), and then essentially forgetting about it for the next few years.
To add an extra layer of protection against cross site scripting. I think one of the recent data leak could have been avoided if the website implemented that very basic header all modern browsers support.
The thing that makes CORS annoying for me is when trying to get a local development environment up and running, or trying to get a standalone web page (e.g. game) running locally. For the latter, you can run a simple web server but its another added impedance that reduces usability in favour of security, like not being able to render an XML file with an xsl preprocessing statement to a local XSLT file.
CORS is primarily a concern for frontend engineers, though its implementation depends on backend. In my experience, many backend engineers struggle to understand it because it's not their concern.
Also, like others have pointed out, CORS or CSP depends on the client (browser) to enforce it. So it doesn't protect against attacker with customized clients.
What is missing is a guide how to replace the bad pattern bey a good one.
Add a function that the user can login, create his individual API key, and store in a a secure way on his client (e.g. any credential store)
Agreed. This is a good article, written well for the intended audience—which I would qualify as occupying that ambiguous space between junior and senior developer knowledge. Perhaps the author hasn’t really arrived at the “right” answer for themselves. You hit the nail on the head, the solution is to login (authenticate).
Maybe the author is being careful not to offer an abstract use case as that might ironically be misinterpreted and be counterproductive? I feel security blogging is just one of those subjects where it’s more useful and prudent to express what shouldn’t be done then should. Maybe it’s okay it doesn’t include the solution, that’s left up to the reader to infer.
It is not useless, because there is information that the browser has and that it is trusted to protect. Consider a server that returns JSON in response to a POST request, and what JSON it returns depends on the user's cookie. Properly configured CORS headers allow the JSON to be read by some origins and not others.
An attacker with curl will not have the user's cookie and an attacker with a malicious website will not have the right origin.
The author misses one very important thing: The user only logs in on user-agents they trust.
CORS is supposed to secure the user’s data. You are NOT supposed to send global server-side data (like secret keys to third party services) through CORS.
Consider that any user data shown “publicly” to all other authenticated users (eg user icon via Facebook’s API) can be used to deanonymize that user, because someone can just create a fake account, exfiltrate the images, and do a reverse image search.
But the author is right, CORS is just one part of the equation. Together with SRI, they can definitely make secure cross-chain interfaces.
The actually insecure alternative back in the day was JSONP. Read my stackoverflow answer from OVER 10 YEARS AGO: https://stackoverflow.com/a/5447005
Since, according to the article, the API key is user-specific, and presumably only a logged-in user would receive the key in the JS source, how would an attacker get hold of it? Just trying to understand the attack vector.
This is why I think it's smart to discourage most developers from rolling their own authentication. Auth has gotten a bit more complex with the rise of SPAs plus API backends (like the setup in the original article), and mistakes you make with auth will bite you much harder. I'm not saying you should never do it, just that we all make mistakes and I'd rather those mistakes happen in a less security-critical context.
If you have any unauthenticated routes that you don't want arbitrary websites calling.
> using JWT in a typical SPA <-> API scenario.
Is this typical? It's a pretty horrible setup.
Cookies have a lot of great features that 'store a JWT in LocalStorage' just doesn't have.
Why the hell does CORS only support exactly one origin domain or wildcard? Such a silly decision to not allow comma separated values or wildcard subdomains.
I get why you think it's silly. I'm pretty sure I thought that too when I first encountered it. But I think it makes sense and there is actually a way to support more than one but not wildcard. Multiple origin domains are supported by using the pre-flight request to change the one domain returned based on what is in the pre-flight request.
I don't know what the actual reason was, but I would guess the design decision was made so the multiple domains use case would be infinitely scalable. Otherwise you would run into header length limitations. Imagine trying to fit 10,000 domains into the header! Eventually you'd need something like this implementation anyways.
although browsers only accept a single origin domain or wildcard as cors header, the server can trivially handle the multi domain use case. on preflight request, check the location header in the request, and if it belongs to a defined whitelist, set the cors header on the response to that domain or subdomain.
I don't know the why, but I can imagine that since there is a reasonable work around, browser implementers perhaps kept things simple and never bothered adding support for a list of specific domains.
[+] [-] goto11|4 years ago|reply
CORS does not protect endpoints against malicious clients, since you can always just make the same request outside of a browser. And it doesn't protect any site from making or receiving cross-site requests, since CORS can always be disabled on the server side.
CORS protect against the scenario where a malicious site tricks an unmodified browser to make a cross-site request to a legitimate site. If the user has an authentication cookie for the legitimate site, the cookies will be sent along with the request. So the malicious site can perform transactions in the legitimate site on behalf of the user, despite not having direct access to the authentication cookie.
CORS is further complicated because certain forms of cross-site requests have always been allowed by browsers, and therefore must remain enabled by default for backwards compatibility. GET requests to separate sites is allowed, since this has always been allowed e.g. to embed images from other domains. POST requests are allowed with the caveat that you can't inspect the result, because you have always been able to initiate a post to a different site from a html form.
[+] [-] JanSt|4 years ago|reply
[+] [-] EGreg|4 years ago|reply
From having to deal with CORS over the years, to me is just a tool to protect one thing… the intellectual property of whatever site we are loading resources from.
For example, images arrive “tainted” and you can’t get their pixels on a canvas no matter how hard you try. You can’t generate a “screenshot” you can print, and so forth.
If this was just about protecting the relying party website, that website would have a way to get around this “protection” to get something done.
Sure, people can try to get at the intellectual property directly, but they’d have to have an authenticated account first. If they do it on the site where it’s hosted, they are subject to its rules. If they are coming from some random site, eg loading it in bulk, the resulting info they get can’t be analyzed by the Javascript.
Yes CORS is just a relaxation of SOP, which makes it impossible to get sensitive data that would have otherwise been revealed to the relying party. Again — SOP is protecting the user’s data loaded from the ORIGIN. Its main job is’t “protecting the receiving site”, that’s a pretty disingenuous characterization.
[+] [-] systemvoltage|4 years ago|reply
Can you please give an example of this?
[+] [-] JimDabell|4 years ago|reply
> This browser implementation can be bypassed at any time. First, it is up to the browser itself: if CORS is not integrated, or not integrated cleanly, then it will not work.
> An attacker can access the API key via the source code of the web app and use it, for example, via a cURL request to directly access the API resources of the backend. With cURL, no CORS takes effect, so the attacker has direct access with the full rights of the user.
All of this is completely wrong.
CORS does not protect anything from anyone. The same-origin policy stops code from one site reading resources from another site. CORS selectively removes that protection – it decreases security.
If CORS is not integrated, the same-origin policy is in full effect and code from one site cannot read resources from your site. It starts off as secure, and stays secure.
If you use cURL, then the same-origin policy doesn’t apply, and you can of course read any resource.
The same-origin policy is not a generic access barrier, and shouldn’t be used as such. It stops one site from abusing the user’s authenticated state with other sites, and that’s it.
[+] [-] fivea|4 years ago|reply
This comment comes off as either disingenuous or needlessly contrarian, and in the process tries to make points that fall somewhere between completely wrong and miopic.
Your personal assertion that CORS somehow decreases security is based on the patently false assertion that at any point in time requests only came from the same origin. Not only was this never the case, this ignores the fact that with the popularity of REST and SPAs and microservices and API-as-a-service, the norm was since switched to having browsers make requests to anything other than the same origin.
So, without CORS, you have a all-or-nothing security model, where the needle would always pend to the "nothing" side. With CORS, that needle can point "all that matters", which is pretty close to the optimal same-site solution. What CORS does is allow developers to abstract away the definition of "same origin" to mean "the origins that I explicitly allow".
You simply cannot claim that allowing only requests to the origins you explicitly allow is an erosion of a security model. That makes no sense. It made no sense when implicitly that list was only comprised of your own domain, it makes no sense now when that list includes reputable APIs that you pay to use.
[+] [-] djm_|4 years ago|reply
It is very confusing and I’m not entirely sure how it ended up like that.
[+] [-] goto11|4 years ago|reply
[+] [-] winwiz|4 years ago|reply
[+] [-] glintik|4 years ago|reply
[+] [-] unknown|4 years ago|reply
[deleted]
[+] [-] 2bitencryption|4 years ago|reply
To my weak-minded brain, the sticking point always comes down to: CORS/SOP is a policy enforced by the browser, correct? The client. The endpoint tells the client what origins should and should not be allowed to make the request. It is up to the client (browser) to decide whether to enforce this policy or not. Is that at least somewhat correct?
[+] [-] jordanlev|4 years ago|reply
[+] [-] notreallyserio|4 years ago|reply
[+] [-] HL33tibCe7|4 years ago|reply
[+] [-] franciscop|4 years ago|reply
[+] [-] numbsafari|4 years ago|reply
[+] [-] nerdponx|4 years ago|reply
[+] [-] pictur|4 years ago|reply
[+] [-] unknown|4 years ago|reply
[deleted]
[+] [-] JanSt|4 years ago|reply
[+] [-] chrismorgan|4 years ago|reply
[+] [-] tshaddox|4 years ago|reply
[+] [-] donohoe|4 years ago|reply
you should never hard-code an API key in this manner.
[+] [-] politician|4 years ago|reply
Getting CORS right involves a couple days of forgetting what I think I know, closely reading how it works (again), writing up a custom middleware for whatever web framework I’m using that time because the OOTB middlewares always have subtle bugs (usually with the Origin header), and then essentially forgetting about it for the next few years.
[+] [-] throw_m239339|4 years ago|reply
CORS is well defined here: it ALLOWS a browser to make certain types of requests to endpoints outside the domain the browsed website comes from:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
Because the browser follows the same origin policy for a number of API (like FETCH):
https://developer.mozilla.org/en-US/docs/Web/Security/Same-o...
Also if you are a developer, please check:
https://developer.mozilla.org/en-US/docs/Glossary/CSP
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-...
To add an extra layer of protection against cross site scripting. I think one of the recent data leak could have been avoided if the website implemented that very basic header all modern browsers support.
[+] [-] rhdunn|4 years ago|reply
[+] [-] paradite|4 years ago|reply
I found comparing CORS with CSP helpful in understanding both concepts: https://stackoverflow.com/a/50726191/1472186 (I wrote the answer)
Also, like others have pointed out, CORS or CSP depends on the client (browser) to enforce it. So it doesn't protect against attacker with customized clients.
[+] [-] outsidein|4 years ago|reply
[+] [-] awill88|4 years ago|reply
Maybe the author is being careful not to offer an abstract use case as that might ironically be misinterpreted and be counterproductive? I feel security blogging is just one of those subjects where it’s more useful and prudent to express what shouldn’t be done then should. Maybe it’s okay it doesn’t include the solution, that’s left up to the reader to infer.
[+] [-] JanSt|4 years ago|reply
Even storing a session token in localStorage is not a problem if you‘re protected against xss (if your not, nothing else will protect you anyway)
[+] [-] egberts1|4 years ago|reply
Once fully understood, it is useless mechanism of security theater.
[+] [-] jefftk|4 years ago|reply
An attacker with curl will not have the user's cookie and an attacker with a malicious website will not have the right origin.
[+] [-] EGreg|4 years ago|reply
CORS is supposed to secure the user’s data. You are NOT supposed to send global server-side data (like secret keys to third party services) through CORS.
Consider that any user data shown “publicly” to all other authenticated users (eg user icon via Facebook’s API) can be used to deanonymize that user, because someone can just create a fake account, exfiltrate the images, and do a reverse image search.
But the author is right, CORS is just one part of the equation. Together with SRI, they can definitely make secure cross-chain interfaces.
The actually insecure alternative back in the day was JSONP. Read my stackoverflow answer from OVER 10 YEARS AGO: https://stackoverflow.com/a/5447005
[+] [-] nobodyandproud|4 years ago|reply
[+] [-] layer8|4 years ago|reply
[+] [-] Aeolun|4 years ago|reply
Presuming that only a logged in uses sees it is the problem. Everybody and their dog sees it, and can impersonate the person whose key it was.
[+] [-] whakim|4 years ago|reply
[+] [-] unknown|4 years ago|reply
[deleted]
[+] [-] valzam|4 years ago|reply
[+] [-] jesstaa|4 years ago|reply
> using JWT in a typical SPA <-> API scenario. Is this typical? It's a pretty horrible setup. Cookies have a lot of great features that 'store a JWT in LocalStorage' just doesn't have.
[+] [-] hsbauauvhabzb|4 years ago|reply
[+] [-] blurker|4 years ago|reply
I don't know what the actual reason was, but I would guess the design decision was made so the multiple domains use case would be infinitely scalable. Otherwise you would run into header length limitations. Imagine trying to fit 10,000 domains into the header! Eventually you'd need something like this implementation anyways.
[+] [-] hirako2000|4 years ago|reply
[+] [-] cerved|4 years ago|reply
My guess is that it's related to subdomains not guaranteeing to be from the same origin.
The result is that people run into errors, Google them, copy paste the top SO result which advices to allow *
Doesn't seem ideal
[+] [-] pshc|4 years ago|reply
[+] [-] unknown|4 years ago|reply
[deleted]
[+] [-] huqedato|4 years ago|reply
[+] [-] klabb3|4 years ago|reply
(Also, I'm pretty sure an api key is a form of authentication.)