おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

CSRFの対応について、rails使いが知っておくべきこと

以前、CSRFについてのエントリを書きました。

CSRFについて - おもしろWEBサービス開発日記

上記エントリではCSRFの概念について書きましたが、もう少しつっこんで調べる必要が出てきました。調べたことを書いてゆきます。

基礎

application.rb(ないし適当なController)にprotect_from_forgeryメソッドを定義すれば、railsが自動的にCSRF対策をしてくれます。というか、デフォルトでapplication.rbに下記のように書いてあるので、特に何もせずともCSRF対策はバッチリなのです。

  protect_from_forgery # :secret => '8ff3ed33f86a431662d8dfe255acdb4a'

railsは、get以外の動詞のリンクに、authenticity_tokenというパラメータを自動的に付け加えます。get以外の動詞の各アクションでparams[:authenticity_token]とsession[:csrf_id]を比較して、同値であればOKとしているようです。*1同値でなければActionController::InvalidAuthenticityTokenという例外がでます。

protect_form_forgeryのオプション
  protect_from_forgery # :secret => '8ff3ed33f86a431662d8dfe255acdb4a'

上記のコードに書いてある:secretというオプションは、セッションをDBまたはメモリに格納しているときにだけ使います。デフォルトはセッションをクッキーに格納している*2ので、:secretはコメントアウトのままにしておきます。

特定のメソッドだけCSRFの対策をしたくない場合は、そのメソッドのあるコントローラでこんな風に定義します。

  protect_from_forgery :except => ["create"]

:onlyも使えます。こうすると、createアクションの時のみprotect_from_forgeryが有効になります。

  protect_from_forgery :only => ["create"]

railsのヘルパを使わずにajaxでdbをいじる方法

CSRFを詳しく調べた理由は、これがやりたかったから。


先ほども書きましたが、get以外の動詞(POST, PUT, DELETE)を使う時は、authenticity_tokenをセットする必要があります。なのでrailsajax用のヘルパを使わずに、独自にjavascriptを書いてdbをいじる時は手動でauthenticity_tokenをいれてあげないといけません。また、PUTやDELETEはブラウザが対応していないので、特殊なやり方を取る必要があります。

authenticity_token

authenticity_tokenの値を入手するにはform_authenticity_tokenメソッドを使います。こんな感じでhiddenに入れてあげると良いと思います。

<%= hidden_field_tag "authenticity_token" form_authenticity_token %>
putやdeleteを使用したい場合

まず、railsは、下記のようにputを使うように定義すると

<% form_for :user, :method => "put">
  # ...
<% end %>

下記のようなHTMLを自動的に作成します。

<input hidden value="put" name="_method" />

そして、リクエストが来たときに、params[_method]の中を見てメソッドを判別しています。なので、javascriptの中で_methodを渡すように書いてあげれば良いです。

まとめ

まず、viewにauthenticity_tokenをセット。次にjavascriptでこんな感じに書きます(jquery使用してます)。ポイントは、

  • _method
  • authenticity_token

の二つをrailsに送信しているところ。

$(function(){
  $("#hoge").click(
    function(){
      $.ajax({
	dataType: "text",   
	data: {              
	  "_method": "put",
	  "authenticity_token": $("#authenticity_token").val()
          "hoge": "hoge"
	},
	type: "POST",
	cache: true,
	url: "http://www.example.com/",
        success: function(data) {
          // ...
        }
      })
    }
  )
});

*1:セッションの格納方法によっては動きが少し違うみたいです。これは近いうちに調べます

*2:このへんまだよくわかっていないので、これも近いうちに調べます