ASP.NET MVC で Ajax.ActionLink を使った場合にも AntiForgeryToken を送信することは・・・

タイトル通りですが、正攻法っぽい方法は無かったので検討だけ。

Ajax.ActionLink の post の動き

ちなみに Unobtrusive で作ってるという前提です。

@Ajax.ActionLink("test", "AjaxAction", new AjaxOptions { HttpMethod = "POST" });

上記のコードは以下にレンダリングされます。

<a data-ajax="true" data-ajax-method="POST" href="/Home/AjaxAction">test</a>

で、これは jquery.unobtrusive-ajax.js では以下のコードで処理されます。

$("a[data-ajax=true]").live("click", function (evt) {
    evt.preventDefault();
    asyncRequest(this, {
        url: this.href,
        type: "GET",
        data: []
    });
});

asyncRequest の中では最終的に jQuery の $.ajax で Xhr を実行します。

@Ajax.ActionLink("test", "AjaxAction", new { ItemId = "001" }, new AjaxOptions { HttpMethod = "POST" });

上記のようにオプションを与えると Url の Query 引数として送信できます。http://hogehoge.jp/Home/AjaxAction?ItemId=001 のような感じですね。

なので、アンカークリックのための Url が生成され、それを POST で jQuery を使った非同期送信するというだけです。

AntiForgeryToken はどのように生成されるか?

コード (Razor) で書けば

@Html.AntiForgeryToken()

のようにとても単純です。で、実際にはどのようなコードが吐かれているかと言えば

<input name="__RequestVerificationToken" type="hidden" value="*****" />

みたいな hidden タグな訳ですねー。ここの value だけさくっと取り出せる API があれば今回の件も苦労しなくて済むのですが、タグで出力されるんですよね。

Ajax.ActionLink で AntiforgeryToken を使うには

さて、ここまでくれば仮に Form タグ内に上記の hidden が含まれていればどう送信されるかも分かります。はい、つまりは Ajax.ActionLink の ajax 転送時にこの hidden の内容ごと送信してしまおうという事ですね。

サンプル

この例がわかり易かったので引用しながら。

http://tpeczek.blogspot.jp/2010/05/using-antiforgerytoken-with-other-verbs.html

AntiForgeryToken を出力しておく

とりあえず1つ画面上にあればOKという事です。

Ajax.ActionLink の送信 data の部分に AntiForgeryToken を追加する

こちらも引用コードです。

<%= Ajax.ActionLink("Delete", "AjaxDeleteItem", new { ItemId = item.ItemId, __RequestVerificationToken = "_" }, new AjaxOptions { HttpMethod = "Delete", UpdateTargetId = "divList" })%>

空白でもいいのですが、後からの置換を考えて「_」と合わせてプレースホルダにしているようです。

画面構築後に入れ替える

こちらもまるまる引用。

$(document).ready(function () {
  //Finding AntiForgeryToken input
  var antiForgeryToken = $('input[name=__RequestVerificationToken]');
  if (antiForgeryToken.length > 0) {
    //Serializing AntiForgeryToken
    var antiForgeryTokenSerialized = antiForgeryToken.serialize();
    //For each anchor in page
    $('a').each(function (index, element) {
      //Replace placeholder with serialized AntiForgeryToken
      $(element).attr('href', $(element).attr('href').replace('__RequestVerificationToken=_', antiForgeryTokenSerialized));
    });
  }
});

AntiForgeryToken が入っているタグを探し、シリアライズしたデータを、配置しておいたプレースホルダと入れ替える。AntiForgeryToken をサーバーに送る事ができるようになります。

ValidateAntiForgeryToken が処理できない

上記まででサーバーに送信されてくる AntiForgeryToken は、Query 引数として送信されてくるだけなので [ValidateAntiForgeryToken] の属性を Controller に付けても処理されない。ValidateAntiForgeryToken は Request.Form に入っている事が前提になっているからです。

引用したサイトでのサンプルでは Delete メソッドでのサンプルのため、ValidateAntiForgeryToken を変えてしまえばいいかも的な感じですが、、Post の場合はどうしようも無いですねー。

ただ、ケースによって Request.Form からとるか Request.Param からとるかを変えるってのを属性定義の段階で制御できるようにすればいけそうな気がしますが、うーーん、そこまでしてやるのかという。。

まとめ

結論からずれば、調査・検討はしてみたものの、トークンが Url に入ってしまうので https でないと怖いですし、バリデーションの部分も結構無理しないと上手くできないのを考えると、普通に Ajax.Form 使ってやる方がよっぽど楽そうです。

という事で検討終わり。

参考リンク