Avoid Special Characters in Rails Form Action Paths
To protect against XSS attacks, Rails generates distinct CSRF tokens per form. A form submitted with the wrong authenticity_token will produce the exception:
ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.)
The form's action path affects the token generation. Before generating the token, the path is normalized to:
- Ensure a leading
/ - Remove the trailing
/ - Ignore the query params
But if the action path includes special characters like |, it will produce a different authenticity token depending on whether the characters are encoded or decoded.
This can lead to hard-to-debug problems where:
- A form works on some web servers but not others.
- A form works when submitted via Ajax but not when submitted as a full page submit.
Say that you require a key in as a path segment and your key allows characters that need to be encoded. For example, if you you generate a form with:
= form_with url: post_path(key: 'weird|value'), data: { turbo: false } do |f|
Then this will produce HTML like:
<form data-turbo="false" action="/posts/weird%7Cvalue" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token" value="YRAax..." autocomplete="off">
On a web server like Apache, the %7C will be decoded to a | before the request gets to Rails. This means that the expected CSRF token will be generated using a slightly different path than what was used when the authenticity_token was initially generated, causing an InvalidAuthenticityToken error.
What can make this confusing is that servers like Puma won't decode the | and the form submission will work successfully. Also, submitting the form using Ajax with data-turbo set to true works without issue.
It's best to avoid special characters in path segments if possible. Values with special characters can be sent in the query string or request body instead.