CSRFトークン(CSRF対策)
ここの内容のまとめ
以下の項目のいずれも満たしていない場合にCSRF対策の検証で弾かれる
設定ファイルでconfig.application_controller.allow_forgery_protectionをfalseに設定している
リクエストのHTTPメソッドがGETまたはHEADである
セッション変数_csrf_tokenの値とリクエストボディのauthenticity_tokenの値の比較が正しい
セッション変数_csrf_tokenの値とリクエストヘッダーのX-CSRF-Tokenの値の比較が正しい
form_forなどRailsのヘルパーを用いたフォームの場合authenticity_tokenがフォーム内に埋め込まれる
remote: trueオプションをつけた場合はauthenticity_tokenがフォーム内に埋め込まれない代わりに、jquery-railsがリクエストヘッダーにX-CSRF-Tokenを設定してくれる
Railsだとform_forなどでPOSTやDELETEなどのリクエストを送信するフォームにはCSRFトークンが埋め込まれている↓
code:html
<input type="hidden" name="authenticity_token" value="ROlprAzxHhPpaDjgF9+rpwJc6e0JsCXmBKzC0sd6BY9ONVGiSFtP6JHMqeH0uac4/eQ9HGJjcQHDTgYPSn6rLg==">
デフォルトでPOSTなどのリクエストを送信するときはこのトークンがないと弾かれる設定になっている。
code:text
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)
ActionController::InvalidAuthenticityToken
だいたいこんな感じでActionController::InvalidAuthenticityToken例外が発生する。
これはなぜ発生するのかというと、セッション変数 _csrf_tokenに比較する値があり、
これがリクエストヘッダのX-CSRF-Tokenの値またはリクエストボディのauthenticity_tokenの値と合致していない場合
例外が発生させられているから。
protect_from_forgeryメソッド内のverify_authenticity_tokenメソッド内のverified_request?メソッド内に
条件のコードが記載されている。
(~メソッド内の...って表現は違う感じするけどこの順番で追って行ったって感じで受け取って欲しい)
code:ruby
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
append_after_action :verify_same_origin_request
end
def verify_authenticity_token
mark_for_same_origin_verification!
if !verified_request?
if logger && log_warning_on_csrf_failure
logger.warn "Can't verify CSRF token authenticity"
end
handle_unverified_request
end
end
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
valid_authenticity_token?(session, form_authenticity_param) ||
end
外部からのwebhookなどを利用する際にCSRF対策が不要であれば、リクエストを受け取るコントローラに
code:ruby
class HogeController < ApplicationController
のようにprotect_from_forgery :exceptでアクションを指定すると、RailsがCSRF対策をしてくれなくなる。
js側でPOSTリクエストを送信したいときは
code:coffee
$.ajax({
url: 'users/admin',
type: 'POST',
data: { authenticity_token: csrf_token, ... }
})
のような形でCSRFトークンをパラメータに含んで送信してやればよい。
$('meta[name=csrf-token]').attr('content');の部分でCSRFトークンを取得するのだが、
これはフォームにremote: trueオプションをつけた時のjQueryでCSRFトークンを取得する処理をそのままパクった。
remote: trueをつけた時の処理はこんな感じ
code:js
/* これはAjax送信を行う処理、一見X-CSRF-Tokenの設定はしてないように見える */
$document.delegate('form', 'submit.rails', function(e) {
var remote = $(this).data('remote') !== undefined;
if (remote) {
/* ...Ajax送信を行う処理... */
}
});
/* ajaxPrefilterメソッド(Ajax送信する直前に実行されるメソッド)でX-CSRF-Tokenを設定していた */
CSRFProtection: function(xhr) {
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
},
/* 省略 */
$.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
でもjQueryに精通してるわけじゃないし正直めっちゃわかりにくい。
とりあえずPOSTリクエストが弾かれる条件とCSRF対策をしつつ回避する手段を知ってればいいかなって感じ。