protect_from_forgeryが何をやっているのか調べた

Rails3でCSRF対策としてApplicationControllerにデフォルト指定されるprotect_from_forgeryですが、実際のところ何をやっているのかわからなかったのでコードリーディングしてみたメモ。

環境

処理の流れ

0. ApplicationControllerに下記の指定がされているところから始まる

class ApplicationController < ActionController::Base
  protect_from_forgery
end

1. まずprepend_before_filterでverify_authenticity_tokenをbefore_filter郡に突っ込む

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 67
def protect_from_forgery(options = {})
  self.request_forgery_protection_token ||= :authenticity_token
  prepend_before_filter :verify_authenticity_token, options
end

2. verify_authenticity_tokenではverified_request?を呼ぶ

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 75
def verify_authenticity_token
  unless verified_request?
    logger.warn "WARNING: Can't verify CSRF token authenticity" if logger
    handle_unverified_request
  end
end

3. verified_request?でリクエストデータを検証する

  • protect_against_forgery?は設定ファイルでallow_forgery_protection = falseが指定されていないか検証
  • GETリクエストか検証
  • sessionに含まれるtokenとリクエストパラメータにrequest_forgery_protection_tokenが指定されていてマッチするか検証
  • sessionに含まれるtokenとX-CSRF-Tokenヘッダのトークンが指定されていてマッチするか検証

の4項目を検証している。

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 93
def verified_request?
  !protect_against_forgery? || request.get? ||
    form_authenticity_token == params[request_forgery_protection_token] ||
    form_authenticity_token == request.headers['X-CSRF-Token']
end

4. 処理の分岐
verified_request?の検証で成功(どれか1つでもtrue)すれば何もしないでprotect_from_forgeryの処理は完了、
失敗(すべてfalse)だった場合はhandle_unverified_requestが呼ばれてさらにreset_sessionが呼ばれて
最終的にリクエストからsession情報がリセットされる。

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 84
def handle_unverified_request
  reset_session
end
# File actionpack/lib/action_controller/metal/rack_delegation.rb, line 22
def reset_session
  @_request.reset_session
end
# File actionpack/lib/action_dispatch/http/request.rb, line 209
def reset_session
  session.destroy if session && session.respond_to?(:destroy)
  self.session = {}
  @env['action_dispatch.request.flash_hash'] = nil
end

まとめ

protect_from_forgeryはリクエストを検証して正しければ何もしない、正しくない場合はsessionをクリアする。