[ActionPack] Feature proposal: configure CSRF to force authenticity_token as header only

Imagine you are developing a rails API for a front-end client. The client is served independently from rails so no asset pipeline is involved. In other words, csrf_meta_tag isn’t an option. So you want to protect your site from XSS and CSRF attacks. To prevent XSS, you ensure that the client takes a JWT header upon successful authentication and stores it as a secure cookie. To prevent CSRF, you add the form_authenticity_token value to a custom header in the response of any GET request to the API. The client includes this CSRF token in all non-GET requests to the API as a custom header. The API supports multiple clients: a react app, an iOS app, and an Android app. For this reason, you cannot effectively enforce cross-origin checks.

Now imagine you’re an attacker. You’ve created a clone of the aforementioned site. With knowledge of the above, you realize that you can fetch the CSRF token via AJAX and include it as a hidden field in your malicious, CSRF form. You call this field authenticity_token. You then trick a victim to visit your site and incidentally bypass CSRF protections because there is no way to turn off the authenticity_token parameter in non-GET requests.

I submitted a proposal PR that implements this: https://github.com/rails/rails/pull/35183

It adds a rails configuration option: request_forgery_protection_force_header. When true, it disables the ability to send a CSRF token as a parameter. Instead, it must be sent as a custom header. However, this option is false by default.

AFAIK the attacker’s plan is foiled as form tags cannot submit custom headers.