何が起きたのか
rails4 -> 5 へのアップグレードガイドに、 rails5 からは protect_from_forgeryは今後デフォルトでprepend: falseに設定される と書かれているので、今までの挙動を変えたくないということで、
- protect_from_forgery
+ protect_from_forgery prepend: true, with: :exception`
に変更。 with: :exception
オプションは prepend: true
を設定したときに RuboCop だかに指摘されたので設定。
そうすると、 ajax で部分テンプレートを返す実装をしている ajax/hogehoge
というエンドポイントで下記のエラーが発生しているよ、というエラー通知が飛んでくるようになった。
ActionController::InvalidCrossOriginRequest: Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript
TL;DR
routes.rb
内のエラーが起きているエンドポイントを設定しているルーティングで constraints オプションを設定し、 XMLHttpRequest
だけを受け付けるようにしてあげればいい。 ajax/hogehoge
みたいなエンドポイントだったら下のような感じ。
namespace 'ajax' do
get 'hogehoge', constraints: lambda { |req| req.xhr? }
end
問題の再現方法
エラーが起きていた、 ajax/hogehoge
にブラウザから直接 ajax/hogehoge.js
といった感じでアクセスしてあげると発生。
起こった問題の原因
-
ajax/hogehoge
はとあるページからの ajax 通信でアクセスされることを想定したエンドポイントだった。 - そのエンドポイントに対し、ブラウザから直接アクセスしたことにより発生。
- rails5 移行の実装を入れる前は、
protect_from_forgery with: :exception
のオプションが設定されていなかったため、エラーは起きているものの例外が投げられずスルーされていた。(ということは ここのコメントを見て予想)
対応方針
一番簡単なのは、 rails4 の時と同じ挙動になるように、
protect_from_forgery prepend: true, with: :null_session
に設定してあげる方法。(未検証ですが多分ちゃんと動く)
ただ、 rails5 の推奨は with: :exception
らしいので、 このオプションを設定した上でどうにかしたい。
また、今回の問題は ajax 通信を期待しているエンドポイントに対して、非 ajax 通信が来たことにより発生しているので、コントローラレイヤとかではなくルーティングのレイヤでどうにかしたい。
対応内容
routes.rb
内のエラーが起きているエンドポイントを設定しているルーティングで constraints オプションを設定してあげる。 ajax/hogehoge
みたいなエンドポイントだったら下のような感じ。
namespace 'ajax' do
get 'hogehoge', constraints: lambda { |req| req.xhr? }
end
constraints オプションに関しては、 railsガイドに詳細が乗っているのでそちらを参考にしてください。
constraints
オプションでは、 受け付けるリクエストに制限を課すことができるので、 lambda
を用いて、リクエストが XMLHttpRequest
(ajaxのリクエスト)であるかどうかを検証している感じです。
この設定により、 ajax/hogehoge
に ajax でリクエストをする場合は、 Ajax#Hogehoge
コントローラーが呼び出され正常にレスポンスが返されるが、 ブラウザから ``ajax/hogehoge.js` などでアクセスした場合は 404 エラーを返すようになる。
終わりに
rails6も出ていることですし、あんまり今回のような問題で詰まることもないかと思いますが、せっかくだったので記事にしてみました。個人的には、 rails のコードを読んだりするいい機会になったので結構楽しかったです。