The feature that is also the vulnerability
Cookies have one behaviour that is both the feature and the vulnerability. Your browser attaches them automatically to every request for their domain, no matter whose page triggered that request.
Picture the attack. A page on some other site contains a hidden form pointing at your bank's transfer endpoint and submits itself on load. The browser dutifully attaches the bank cookie, and the bank sees a well-formed authenticated request.
The attacker never read the cookie. They simply spent it.
Run at least two of the three defences. Marking your cookies as same-site stops the browser attaching them to cross-site posts. Browsers making that the default in 2020 quietly killed the classic drive-by version of this for most of the web.
Put a random value in each form or header that an attacker's page cannot read, which your framework middleware handles for you. And check the origin header, rejecting anything initiated from a foreign site outright.
Header tokens, and their own cost
Understand why tokens in a header are immune to this. Nothing attaches them automatically, so a hostile page cannot spend what it cannot set.
Read the relocated cost that comes with that honest advantage. Your token now has to live somewhere scripts can reach, and browser storage is readable by any script running on your page. One injection hole, one compromised dependency, and every user's token is exfiltrated for later use.
Compare a cookie marked unreadable by script, which cannot be read at all. An injection can still fire requests as your user while the tab is open, since nothing fully survives that. The credential itself cannot be carried away and replayed from somewhere else.
Build the defensible browser setup. Credential in a cookie that is unreadable by script, secure and same-site. Cross-site protection on anything that changes state. And a real content security policy.
Fall back carefully if you must use header tokens. Hold the access token in memory only, keep the refresh token in a cookie script cannot read, and reserve browser storage for things you would not mind an attacker holding.
Worked example
In 2008, Princeton researchers William Zeller and Ed Felten published working CSRF attacks against four production sites, the worst being ING Direct, then one of the largest US online banks. Their proof of concept was a page that, when visited by a logged-in ING customer, silently created a new account in the victim's name and transferred money into it, then out to an account the attacker controlled, every step a forged POST that the browser authenticated by attaching the victim's session cookie. No password was stolen and no TLS was broken; the browser did exactly what cookies are designed to do. ING fixed it within days of disclosure, and the same paper documented holes in NYTimes.com and YouTube. SameSite cookies didn't reach browsers until roughly 2016, so for years, per-request CSRF tokens were the only wall, and the incident is why frameworks now refuse to let you forget them.