読者です 読者をやめる 読者になる 読者になる

Rails にて XMLHttpRequest で POST するとセッションが切れる

ruby

 ご多分に漏れずハマったので書いておきます。(Ruby 1.9.3, Rails 3.2.2)
 Rails にはセッション変数 session がありますが、タイムアウトでもないのにこれが何故か空になってしまう現象が発生して、かなり悩んでました。どうも JavaScript で直接 XMLHttpRequest 発行すると発症するようです。GET ではなく POST したときのみのようです。
 解決策ですが、リクエストヘッダに 'X-CSRF-Token' を追加すれば良いようです。

function post_xhr_request( url, param, on_ready )
{
  xhr = new XMLHttpRequest();
  xhr.open( 'POST', url, true );
  xhr.setRequestHeader( 'X-CSRF-Token', /* token */ );
  xhr.onreadystatechange = on_ready;
  xhr.send( param );
}

 で、↑ソース中の /* token */ に渡すものを調達しないといけないのですが、これはたいてい application.html.erb に書かれている <%= csrf_meta_tags %> の部分から取得します。erb の実行結果がどうなってるかっていうと例えば↓こんなんが出力されてるハズです。

<meta content="authenticity_token" name="csrf-param" />
<meta content="5f219rF+EYm1D7597eZbviYwm0bM/1viNbjtaXkf47w=" name="csrf-token" />

 ↑ここの content の値を先ほどの /* token */ のところにセットしてやれば OK です。これでセッションが切れなくなります。生々しく JavaScript を書くなら getElementsByTagNames( "meta" ) で meta タグの集合を取って name が csrf-token になってるものの content 属性を取得... という流れになります。JavaScript のコードは使ってる DOM 操作ライブラリによって異なると思うので省略。

 最近は Rails を触っているのですが、僕はこれまで Web サイト作った経験が無いので、こういうトコロ含めて細かいトコロでよく詰まります。


追記
 この現象の原因を @punytan が教えてくれました。

 なるほど〜、CSRF ですか。... ってまぁ僕は CSRF を知らないワケでw。

CSRF【Cross Site Request Forgeries】(クロスサイトリクエストフォージェリ
Webサイトにスクリプトや自動転送(HTTPリダイレクト)を仕込むことによって、閲覧者に意図せず別のWebサイト上で何らかの操作(掲示板への書き込みなど)を行わせる攻撃手法。

CSRFとは 『IT用語辞典 e-Words』

 ほほ〜。でもそれって XSS とはどう違うの?ってググってみたらズバリの記事が見つかりました。
『2007-12-03 クロスサイトスクリプティング(XSS)とCSRFの違い早分かり』ockeghem(徳丸浩)の日記

 CSRF 対策は Rails の 2.x と 3.x で少し変更があったみたいなのですが、ググっても 2.x の記事ばかり。3.x を扱った記事でいうと↓ここが良い感じでした。
『RailsのCSRF対策』yarbの日記