None of the answers here address the core of the issue, so I will try.
Update: Actually OWASP offers this simple way to leverage CORS to successfully prevent CSRF: include a custom HTTP request header with your requests, and check for it on the server! Apparently this causes a preflight check, and that whitelists the domain. So that may be enough!
CSRF
An honest user will use a standards-compliant browser so their access and secrets would be only be communicated by the server in response to an authenticated request, eg with a session cookie bearer token, or signed with a private key.
The way cookies work, they used to be sent out with requests to sites even on domains not matching the top-level domain of the current document. Thus, for example, a foreign site could initiate a POST to your bank (eg via a < form > element with an invisible submit button) and take an action if you had a cookie. This whole class of attacks is mitigated when need to run Javascript to sign a request on the client. You can either do this with an HMAC (using a symmetric key stored on both server and client instead of a bearer token like a session cookie) or you can do it with Assymetric Cryptography, eg elliptic curve signatures using a non-extractable private key (see “subtle web crypto” APIs that come with every browser).
If your server expects requests to be signed, you can dispense with anti-CSRF techniques like double-submitting cookies or session nonces, all of which are there to mitigate attacks made possible by relying on a single bearer token (session cookie) to authenticate a a request.
Since your server refuses to honor requests that aren’t properly signed, you don’t need to worry about CSRF anymore. But since a lot of client-side code is simple, eg mere HTML or simple JS, then requests (eg for images, or large POST requests) might not be signed. And thus that web server will still honor requests with just a bearer token / cookie. It is in this case that you want the CSRF protection — at least with a nonce. (Which, by the way, does NOT need to be generated and stored in a session, it can be calculated by taking HMAC of the sessionId cookie, for instance).
Denying Requests
You can go further and instruct your webserver to refuse all requests originating from foreign domains, so that people don’t eg hot-embed your images (even in no-cors mode) in their site. You’ll still be open to DDOS.
In fact, if you want to help prevent DDOS, your server should generate its own session IDs, with expiration dates and sign them with an HMAC so you can quickly reject requests without properly signed session keys. You can do this rejection at the edge, eg CloudFlare workers, or at least in your local proxy (varnish or nginx).
CORS
CORS on the other hand does not by itself prevent the above class of attacks, where a POST request can be submitted with cookies and your server will need to ignore it. Because, for example “simple requests” (from the original CORS spec) that look like GET or POST submitted by a < form > element are sent without any preflight checks! See https://stackoverflow.com/questions/39725955/why-is-there-no-preflight-in-cors-for-post-requests-with-standard-content-type
You need to implement the above mitigations, expecting signed sessions etc. otherwise if a bearer token / cookie is ever stolen then a MALICIOUS user using a MALICIOUS user-agent can send bad requests, using just that bearer token. But, the hope is simply that an honest user won’t easily get their session ID bearer token stolen. Another thing you can do these days on your server is to set the SameSite: Strict attribute in your Set-Cookie header, which should also mitigate any malicious CSRF exploits.
Now, the main purpose of CORS has been to prevent foreign domains from exfiltrating content from your server, and that includes JSONP. The CORS functionality is not to protect the foreign site from your server, such as XSS exploits. Because like all web tech, it is your server that calls the shots, and thus chooses to benefit or not. So if you have, say, copyrighted content, you can require an authenticated request to access the content, and also use CORS to prevent the client code loaded from that server’s domain from reading it and exfiltrating it.
This is all designed so that an honest web end-user using a standards-compliant user agent will be able to view information from your server, while the server hosting a remote website, even one embedding your content, is not.
Overall Security and Economic Analysis
When it comes to attacks, modeling threats and incentives, the question you should ask is “cui bono?” Who benefits?
CSRF and CORS, like most Web browser technologies was originally designed to help Web SERVER operators secure their networks and provide a mechanism to give assurances to honest users of their networks.
An honest user will tend to use a mainstream, standards-compliant user-agent (eg browser) in order to access websites. It is in this environment that an honest user will authenticate a session, by entering a password, signing a webauthn payload with a private key, or whatever.
On the other hand, a malicious user may create a fake account and use a browser that doesn’t comply. The goal is to isolate such a user’s attacks so that they can only really hurt themselves. A major theme there is to make everything a zero-sum game, and not allow any “credits to be minted for free”, such as free clicks on ads (click fraud), free tier (DDOS), free messaging (SPAM), etc.
For a much broader approach to micropayments and economics, preventing a whole host of attacks, and actually monetizing digital content across websites in an open and interoperable manner, see https://qbix.com/ecosystem