PostmanでRequest Paramsに入力した+がスペースに変換されてしまうのを防ぐ

小ネタ。

Railsのフォーム経由でリクエストするときはAuthenticity Tokenを付与する必要がある

CSRF対策として、Railsはフォームから送信されたリクエストを処理する前に、渡ってきたAuthenticity Tokenが正しいかチェックする。

railsguides.jp

form_withなどで生成したFormタグには、CSRF対策に生成されたトークンが<input type=hidden>に追加されるので、それをauthenticity_token paramに乗せて渡す必要がある。

f:id:osamtimizer:20200120150704p:plain
トークンが格納されているinputタグ。valueは適当。

素朴なPOSTリクエストを行うフォームであれば、そのままSubmitするだけで勝手に付与されるのだけど、JavaScriptやPostmanなどのHTTPクライアントから送信する場合は自前でparamsに設定してやる必要がある。

ここで問題が

PostmanのParamsはvalue+が含まれていた場合、スペースとして認識されてしまう。

f:id:osamtimizer:20200120151121p:plain
Postmanの QueryParams

# Railsのログ
Parameters: {"authenticity_token"=>"123 test=="}
Can't verify CSRF token authenticity.

解決策

+ではなく、%2Bエンコードした状態で送信する必要がある。

f:id:osamtimizer:20200121000117p:plain

Parameters: {"authenticity_token"=>"123+test=="}

なぜこうなる?

github.com

上記のIssueに書いてある通り。

In query params '+' needs to be encoded to %2B instead of %20 - This is the expected behavior as + has a special meaning of (space) in query params which needs to encoded as %20 (space).

クエリパラメータに含まれる+は%2Bではなく%20にエンコードされる。これはPostman側で想定している挙動らしい。

なぜかというと、HTMLの仕様の 17.13.4 Form content typesにこんな記述がある。

www.w3.org

application/x-www-form-urlencoded
This is the default content type. Forms submitted with this content type must be encoded as follows:

Control names and values are escaped. Space characters are replaced by +, and then reserved characters are escaped as described in [RFC1738], section 2.2: Non-alphanumeric characters are replaced by %HH, a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as "CR LF" pairs (i.e., `%0D%0A').

どうやら、クエリパラメータに含まれているスペースは+に置換されるらしい。で、クエリパラメータの+はスペースを表すので、パラメータに+が含まれた文字列を指定するとこのような結果となる。

おわりに

Postmanで+をちゃんと送りたいだけの小ネタ記事だったはずなんだけど、クエリパラメータとかHTMLの仕様とか調べてたら意外と時間かかった...。