AjaxのCSRF対策

SPAで非同期通信を多用すると思います。その際に、非同期通信の場合のCSRF対策について調査ました。

通常の同期処理(ページ遷移)の場合

ASP.NET MVCではCSRF対策が超簡単です。
ViewのFormの中でRazorの@Html.AntiForgeryToken()を呼ぶだけでトークンを埋め込むことが出来ます。
トークンを検証するにはControllerでActionMethodの属性にValidationAntiForgeryTokneを指定するだけです。

View

@using (Html.BeginForm()){
    @Html.AntiForgeryToken()
    // 略
}

Controller

[ValidationAntiForgeryToken]
[HttpPost]
public ActionResult Create(ViewModel model){
    // 略
}

ところが、非同期処理(Ajax)の場合は少々工夫が必要になります。

非同期処理(Ajax)の場合

Razorでクッキーのトークンとフォームのトークンを発行する関数を作成します。
作成した関数を呼び、hidden値に設定し、POST時にヘッダに付加してリクエストを送信します。

View

<script>
    @functions{
        public string GenerateRequestVerificationToken()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;
        }
    }
</script>

<input type="hidden" id="requestValificationToken" value="@GenerateRequestValificationToken()" />

JS

$.ajax({
    url: http://test/Create,
    type: "POST",
    dataType: "json",
    data: {
        data: hoge
    },
    headers: {
        'RequestVerificationToken': $('#requestVerificationToken').val()
    }
});

ヘッダに付与されたトークンを検証する関数を用意します。

private void ValidateAntiForgeryToken()
{
    string cookieToken = "";
    string formToken = "";
    var tokenHeader = Request.Headers["RequestVerificationToken"];
    if (!String.IsNullOrEmpty(tokenHeader))
    {
        string[] tokens = tokenHeader.Split(':');
        if (tokens.Length == 2)
        {
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        }
    }
    AntiForgery.Validate(cookieToken, formToken);
}

ControllerのActionMethodで先ほどの関数を呼びます。

public ActionResult Create(){
    // 自前でヘッダに付与されたトークンを検証する
    ValidateAntiForgeryToken();
    // 略
}