半年前に会社の技術ブログの下書きに雑に書いてて公開しどきを忘れたやつ、もったいないのでこちらで公開。
Rails の API モードはデフォルトでは CSRF 対策機能が有効になってないので、クッキーによるセッション機能を使うと脆弱性を生む可能性がある。
Rails に実装済みの CSRF 対策機能を API モードで使う方法を紹介します。
やること
Rails の CSRF 対策機能を有効にする
ActionController::RequestForgeryProtection
をインクルードprotect_from_forgery
を有効にしたいコントローラに追加
クライアントに CSRF トークンを返す
セッション生成直後に { authenticity_token: form_authenticity_token }
をクエリストリング or レスポンスとして渡す(#form_authenticity_token
メソッドで rails が生成した CSRF トークンが取れる、ちなみにセッション内の CSRF トークンは session[:_csrf_token]
で取れるが、どちらかは暗号化もしくはハッシュ化されてるので値は違うものになる)
クライアント側で POST する際に CSRF トークンを渡す
フォームから POST する場合
<input type="hidden" id="authenticity_token" name="authenticity_token"></input>
を仕込んで js で getElementById とかして先ほどクエリストリング or レスポンスで渡された authenticity_token を value に突っ込む
Ajax や fetch の場合
ちなみに rails の ujs を使わずに ajax リクエスト送る時は X-CSRF-Token というヘッダに csrt-token を自前で含めないといけない
ujs では ここ でやってる。
クロスオリジンの検証を無効にする
このままだと request.base_uri と request.origin が違う(クロスオリジンになってる)と InvalidToken のエラーになってしまうので、 config.action_controller.forgery_protection_origin_check = false にする(これは Rails 4 までデフォルト false だったが Rails 5 以降は true になってる)
CSRF を有効にしたセッションのテストをする
ポイントは以下。
- セッションを作成するエンドポイントを叩く
- csrf の authenticity_token を params で渡す(これしないと session 取れない)InvalidToken が raise しなかったのが気になる
- テスト環境の config.cache_store を null_store -> memory_store に(null_store は本当に cache_store を使わないことを意味する)
- 👆 の cache の後片付けも忘れない