Help us understand the problem. What is going on with this article?

Rails 4.0だとCSRFトークンでエラーになる

More than 5 years have passed since last update.

ついにRails 4がリリースされたので軽く触ってみたら、3.xから変わったところを見つけたので共有。まだ日本語の情報は見当たらなかった。

APIを試しに作ってみようと思いcurlでPOSTリクエストを送ろうとしたら以下のようなエラーが。

$ curl -X POST -d "name='hoge'" http://localhost:3000/bikes
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 1ms

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
...

Rails 3.xのときはWarningは出たものの、エラーにはならなかったような…。

application_controller.rbを見てみると、以下のようなコメントがありました。

application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

どうやらAPIを作りたい場合は、:exceptionではなく:null_sessionを使うといいようです。

気になってactionpackのソースコードを読んでみました。Rails 3.xのときにソースコードを読んだときの記事を最後に載せたので参考にしてみてください。

actionpack-4.0.0/lib/action_controller/metal/request_forgery_protection.rb
def protect_from_forgery(options = {})
  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
  self.request_forgery_protection_token ||= :authenticity_token
  prepend_before_action :verify_authenticity_token, options
end

3.xのときと比べると32行目のforgery_protection_strategyというのが新しく追加されたようです。withオプションで指定したクラスをセットしているようなので詳しく見てみます。

actionpack-4.0.0/lib/action_controller/metal/request_forgery_protection.rb
def protection_method_class(name)
  ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
rescue NameError
  raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
end

デフォルトのように:exceptionが指定されてる場合はExceptionクラスが、今回のようにAPI用に使う:null_sessionが指定された場合はNullSessionクラスがどこかに定義されているようです。

actionpack-4.0.0/lib/action_controller/metal/request_forgery_protection.rb
class Exception
  def initialize(controller)
    @controller = controller
  end

  def handle_unverified_request
    raise ActionController::InvalidAuthenticityToken
  end
end

あったあった。どこかのタイミングでhandle_unverified_requestが呼ばれて、冒頭のように例外が発生するわけですね。

actionpack-4.0.0/lib/action_controller/metal/request_forgery_protection.rb
class ResetSession
  def initialize(controller)
    @controller = controller
  end

  def handle_unverified_request
    @controller.reset_session
  end
end

さらにResetSessionというクラスも見つかりました。これは例外を発生させる代わりにセッションをリセットするみたいです。これはRails 3.xのときと同じ挙動だったと思います。

actionpack-4.0.0/lib/action_controller/metal/request_forgery_protection.rb
class NullSession
  # ...

  # This is the method that defines the application behavior when a request is found to be unverified.
  def handle_unverified_request
    request = @controller.request
    request.session = NullSessionHash.new(request.env)
    request.env['action_dispatch.request.flash_hash'] = nil
    request.env['rack.session.options'] = { skip: true }
    request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
  end

  # ...
end

で、NullSessionクラスを見てみると、NullSessionHashオブジェクトとNullCookieJarオブジェクトというのが出てきますが、こいつらはどうやら中身が空っぽのモックオブジェクトっぽいです。


参考

Rails 3.xのときにprotect_from_forgeryの中身を追いかけた記録です。

CSRFトークンの検証プロセス

repro
世界59か国6,500以上の導入実績を持つCE(カスタマーエンゲージメント)プラットフォーム「Repro(リプロ)」を提供
https://repro.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away