It seems to me that the primary goal of CSRF is to confirm that the client making the request is the client we expect.
The solution I've commonly seen is:
It makes a lot of sense to me that the server is generating the CSRF for (3)(1), but I cannot come up with a reason why it's necessary for (3)(2).
Instead, if the client is pure javascript, I believe this is safe:
My understanding is that 3 and 4 are both things an attacker cannot do, so this would also sufficiently block attacks. Is that correct?
If that is safe, do we even need to do step (1) and (2)? Would this be safe as well because of same-origin policy (assuming cors is configured properly)?
Yes, both of those simplified approaches should be safe in the presence of CORS and the same-origin policy. In fact, you don't even need the CSRF-Safe: true
header as long as you validate the content-type.
Wikipedia confirms:
If data is sent in any other format (JSON, XML) a standard method is to issue a POST request using XMLHttpRequest with CSRF attacks prevented by SOP and CORS; there is a technique to send arbitrary content from a simple HTML form using ENCTYPE attribute; such a fake request can be distinguished from legitimate ones by text/plain content type, but if this is not enforced on the server, CSRF can be executed[12][13]