Let's write β

プログラミング中にできたことか、思ったこととか

rspecでCSRFチェックのスキップを確認する

クッキーを使わずに毎度認証情報をヘッダーに付与してもらう形式のステートレスなRestfulAPIを提供する場合は、 skip_before_action :verify_authenticity_tokenのようにCSRF Tokenの検証をスキップしたいです。 今回、開発中にあやまって一部のAPIでスキップをしわすれていたのですが、その事に自動テストで気がつく事ができなかったため、 そういった誤りをしている場合に自動テストで検出されるようにしました。

CSRF Tokenの検証はallow_forgery_protectionフラグによって制御される

Railsのプロジェクトを生成した再に、test環境ではallow_forgery_protectionフラグがfalseになっています:

# config/environments/test.rb
config.action_controller.allow_forgery_protection = false

こちらのallow_forgery_protection

rails/request_forgery_protection.rb at dabb587cbb279b102ffddb82456140b951d34d91 · rails/rails · GitHub

      def protect_against_forgery? # :doc:
        allow_forgery_protection
      end

で利用されており、

rails/request_forgery_protection.rb at dabb587cbb279b102ffddb82456140b951d34d91 · rails/rails · GitHub

      def verified_request? # :doc:
        !protect_against_forgery? || request.get? || request.head? ||
          (valid_request_origin? && any_authenticity_token_valid?)
      end

GETとHEADのリクエスト以外で、CSRF Tokenの検証を実施するかどうかの分岐につかわれています。

rails/request_forgery_protection.rb at dabb587cbb279b102ffddb82456140b951d34d91 · rails/rails · GitHub

      def verify_authenticity_token # :doc:
        mark_for_same_origin_verification!

        if !verified_request?
          if logger && log_warning_on_csrf_failure
            if valid_request_origin?
              logger.warn "Can't verify CSRF token authenticity."
            else
              logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
            end
          end
          handle_unverified_request
        end
      end

そのため、CSRF Tokenの検証は、test環境ではcontrollerでskipしているしていないにかかわらず実施されない事になります。

CSRF Tokenの検証をテストケースの前後で有効にする

そのため、今回はテストケースの前後で、自動的に、allow_forgery_protectionをtrueにしてケースを実行し、falseに戻す処理を追加するモジュールを定義しました:

# spec/support/api_request_example_group.rb

# frozen_string_literal: true
module ApiRequestExampleGroup
  extend ActiveSupport::Concern

  included do
    around do |ex|
      ActionController::Base.allow_forgery_protection = true
      ex.call
    ensure
      ActionController::Base.allow_forgery_protection = false
    end
  end
end

このモジュールを、APIのテストケースのspecファイルでincludeする事によって、APIのテストに関してはCSRFの検証が実施されるようになり、 適切にskipできていない場合にはCSRF Tokenの検証失敗の例外があがり、ミスに気がつけるようになりました。