【C# mvc】AjaxでJSON送信する場合に[ValidateAntiForgeryToken]を使用する方法

前回の「Oracle12cDB(64bit)とClient(32bit)を同一Windowsサーバ内で同居させる方法」と同じく、「大変だったのでムカつきがてら記事にする」シリーズの第2弾です。

C# mvcでは、コントローラー側のActionResultに [ValidateAntiForgeryToken] 属性を付け、cshtml側の <form> 内に @Html.AntiForgeryToken() と記述すれば、簡単にCSRF対策ができます。

ざっくりいうと、クライアント側でPOST時に発行した「トークン」をコントローラー側で認証し、クライアント側で発行したトークンだと認められたリクエストなら実行される、というものです。

これが .cshtmlと .csの間なら上記のように簡単なのですが、JavaScriptに記述されたAjaxから、JSONを送る時にもこの認証を効かそうとすると、なかなか大変だった、という話です。

(今回も)先に結論

Visual Studio 2017(C# バージョンは7)での動作です。

コントローラー

[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Hantei(hogeDTO model)
{
    try
    {
        // 処理

    }
    catch (Exception e)
    {
        // 処理

    }

    return Json(model);
}

cshtml

....
<form>
    @Html.AntiForgeryToken()
</form>
....

JavaScript

....
$('button[name="HanteiBtn"]').on('click', function() {
    $.ajax({
        url: 'hogeSite/Hantei',
        contentType: 'application/x-www-form-urlencoded',
        dataType: 'JSON',
        data: {
            _RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val(),
            model: {
                UserID: $('input[name="UserID"]').val(),
                InputAreaOpenFlg: $('#IsOpen').val(),
            },
        },
    })
});
....

ポイント

コントローラー側とcshtml側は、特に変わったことはありません。ポイントはJS側、Ajaxのパラメータです。

  1. contentType は ’application/x-www-form-urlencoded’ (’application/json’ ではダメ)
  2. data にトークンとモデルクラスを収める
  3. 上記のモデルクラスに詰め込む際には、JSON.stringify() を使ってはいけない

1.をしないと、コントローラー側が「トークンも含んだPOSTかもしれない」と認識できないようです。単にJSONを投げつけられているとしか認識しない模様。

2.について、ネットを調べるとトークンを header に入れるべきと書かれているところもありますが、data に含めるべきであるようです。

3.について、”model: JSON.stringify({ UserID: …})” のように書いても、コントローラー側には到達します。しかしその場合、model の値はすべて null です。data に複数項目詰め込む際に JSON.stringify() を使用すると、どうやらコントローラー側で認識できないようです。今回の対応以前は JSON.stringify() を使って問題なくコントローラー側で中身を認識できていたので、data に複数項目あるとNGのようです。

大変だった理由

いおぶろぐも当初普通に「c# ajax json validateantiforgerytoken」とかで検索かけてみたわけですが。対象としているC#バージョンが異なることもあってか、それぞれ言っていることが重なってたりバラバラだったり…。という中から、使っているC#バージョンと、既存コードでの修正量を一番抑える方法を模索しなければならなかったのが大変でした。

大変だった理由(本音)

そもそもこの「ajax通信分にもValidateAntiForgeryTokenを」の対応は、静的解析ツールの指摘によるもので、つまりは仕様書に載ってない部分なんですよね。それが納品数日前に発覚し、燃え盛る開発部隊にわずか1月前に投入されたいおぶろぐ(しかもweb系にまったく明るくない)が「なるはやで」の無茶ぶりで投げられたのが、ね…。

とはいえいおぶろぐ自身も「半日くらいかな」と思っていたのが丸々1日かかってしまうとは不覚でした…。でも「ポイント」で挙げた3点に絞るまでも結構紆余曲折あったんですよ…ブツブツ…。