The security impact of automatic client-side expiry is tiny, since token expiration must be done server-side anyway.
The HttpOnly flag as an XSS mitigation is almost useless; competent attackers will simply run their code from the victim's browser and session. To protect against XSS, HttpOnly doesn't really help you at all. You should be setting a CSP that prevents inline and 3rd party scripts by default, and whitelist what you must.
Overall, cookies may seem like they have a lot of security features, but in reality they are just patches over poor original design. IMHO, using local storage is probably better, because there's less room to get it wrong.
Here's one glaring problem with local storage: literally any script on your page can access it (for example, vendor scripts). Cookies can only be accessed by scripts from the same domain from which they're created.
Why should those scripts limit themselves to stealing tokens when they can send authenticated requests from the browser? To put it another way, why would you care about knowing the root password when you have a way to run a root shell at will?
Let's get specific: let's say you have a page on mysite.com. When a user signs in, the server sets a HttpOnly session cookie to authenticate later requests from the user.
Now let's assume your page loads evilsite.com/tracker.js. The code in tracker.js can now send requests to mysite.com, and your HttpOnly session cookie will be sent. There is no extra protection for cookies that would check if the JS code doing the sending came from mysite.com.
Obviously tracker.js cannot read the value of your session cookie (and, indeed, neither can your own code), but mysite.com is more or less totally compromised.
If you don't set HttpOnly on your cookies and ignore the cookie header on your backend (i.e. only use cookies for storage, not for transport), cookies are strictly better than local storage, since the only difference between the two is now local storage's lax access policy.
The scenario you're describing can also be solved by using a CSRF token retrieved from the backend. Meanwhile, there is literally no way to secure secrets kept in local storage from third party scripts.
I don't believe a situation exists where using cookies for client-side storage is more secure than local storage. Could you please explain this in more detail?
Now assume your page loads evilsitem.com/tracker.js. They can send requests to your backend, and the cookie will be included, but since your backend ignores the cookie header it doesn't matter. The malicious script, however, cannot access the cookie directly, since the script's origin is not the same.
That's why I say cookies used this way are strictly more secure than local storage: the fact that they're included with every request is irrelevant, and they're protected from direct access by third party scripts. Local storage is not. Even if you use JWT for auth, you should still store it in a cookie.
The same-origin policy only limits access between windows and frames. All scripts loaded on a page will have the same "origin".
What do you mean? JS even on the same page can't read HTTPOnly cookies. If you are assuming that the browser has been hacked then it is pretty much game over regardless of what you use.