If your app is a Single Page App and it doesn't rely on HTTP/REST (e.g. it uses WebSockets instead), then you can easily use localStorage instead of cookies for holding session IDs/JWTs on the client side (and adding your own logic to pass them to the server) - That's another way to sidestep the issue and it works in all modern browsers.
It makes a case for companies to use WebSocket-based APIs.
Also localStorage works better on mobile when using frameworks like Cordova, React Native, PhoneGap and others - That is because your .html files are usually sitting on the mobile device itself (so the local domain for the file won't match the one where your REST API/backend is hosted; so cookies won't be sent and don't work).
The big issue using localStorage for authentication token storage is that values are accessible by any JS from the same origin as the JS that sets the value. Example: I bundle my JS and it's dependencies into a single file (bundle.js) using webpack. One of the dependencies in the bundle has malicious JS that sends values in localStorage to a remote server, or uses the authentication token to make requests impersonating the user.
Exactly, they can still make requests to your back end by hijacking your session with your cookie; even if you use the httpOnly flag.
The only advantage of the cookie (with httpOnly) in this scenario is that the malicious code can't access your session ID and use it for later (but they can still hijack the session in-place without knowing what your session id is)...
Since sessions expire anyway, there is a sense of urgency; because of this, an effective XSS attack would typically be carried out in-place on the page (while the session is active). So in practice, there is very little added security value in the cookie approach.
In my opinion, XSS mitigation is the last barrier of defence.
No, you can set cookies to HTTP only, so Javascript can't access them. JS injected into a domain can do something with your permissions for that domain by making more requests to the target domain that have the cookie attached, but at the moment that's basically how the Web security model works, so that is in some sense not a hole. But the injected JS can't steal the cookie and send the contents somewhere else.
Why would an XSS attacker need to steal a temporary session ID from a cookie (which will probably expire soon), if they can just highjack the session right there on the spot from the user's own browser?
Not exactly 'anytime' because the session will expire as soon as the user logs out. Even if the user doesn't log out, the session will typically timeout on its own anyway (at least if the auth is implemented correctly).
It's not the protocol you use to communicate with the server that allows CSRF, it's the practice of authenticating based on a value in a cookie. Most of the time this is because you want to use built-in anchor tags and forms to communicate with the server instead of JavaScript/AJAX. The default behavior is for your server's cookies to be sent along with every request to your server, no matter where the request came from, so hello CSRF.
If your JavaScript adds a custom header to every HTTP request with a secret that you keep in localStorage, and your server always authenticates requests by checking that header instead, you can prevent CSRF attacks without switching to WebSockets.
Always be careful to avoid serving or running untrusted JavaScript, of course.
It makes a case for companies to use WebSocket-based APIs.
Also localStorage works better on mobile when using frameworks like Cordova, React Native, PhoneGap and others - That is because your .html files are usually sitting on the mobile device itself (so the local domain for the file won't match the one where your REST API/backend is hosted; so cookies won't be sent and don't work).