ASP.NET Core MVC で追加されたAutoValidateAntiforgeryToken属性が便利

はじめにCSRFありき

クロスサイトリクエストフォージェリ(Cross site request forgeries、CSRF)と呼ばれる脆弱性があります。
不正なリクエストを正規のものとして扱ってしまうという脆弱性です。

ASP.NET MVCでは、フォームに対するCSRF対策としてワンタイムトークン生成&検証機能が用意されています。

ValidateAntiForgeryToken属性について

従来(MVC5)では、CSRF対策においてはValidateAntiForgeryToken属性を用いるのが一般的でした。

コントローラー(SomeController.cs)

public class SomeController : Controller
{
  public static readonly string Name = "Some";

  public IActionResult Index()
  {
    return View();
  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public IActionResult PostAction()
  {
    return View("Index");
  }
}

ビュー(Index.cshtml)

@using Some = PracticeWorld1.Controllers.SomeController

@using (Html.BeginForm(nameof(Some.PostAction),Some.Name, FormMethod.Post))
{
  @Html.AntiForgeryToken()
  <input type="submit" value="Submit"/>
}

ビューでの@Html.AntiForgeryToken()が、CSRF対策のワンタイムトークン(hidden input)を生成し、
また、ValidateAntiForgeryToken属性が付与されたPostActionが、アクション実行時にトークンの検証を行います。

生成されるHTMLは下記のようになります(フォーマット済み)。

<form action="/Some/PostAction" method="post">
  <input type="submit" value="Submit"/>
  <input name="__RequestVerificationToken" type="hidden" value="CfDJ8F1ElGvP...." />
</form>

開発者ツールにて、上記__RequestVerificationTokenの値を書き換えてSubmitすると500エラーが発生します。f:id:mrgchr:20161115223208p:plain

もちろんこの機能はASP.NET Core MVCにおいても有効であり、上記をタグヘルパーで記述すると以下のようになります。

<form asp-action="@nameof(Some.PostAction)" asp-controller="@Some.Name" method="post"
      asp-antiforgery="true">
  <input type="submit" value="Submit" />
</form>

これはこれでいいのだけど

これはこれで便利なのですが、この@Html.AntiForgeryToken()やasp-antiforgery="true"について考えると、
ValidateAntiForgeryToken属性が付いている(Post)アクションを呼び出すときは必須のものです。

忘れると、400BadRequestがこんにちわ!します。

f:id:mrgchr:20161115223530p:plain

うっかり忘れてこんにちわ!した人も多いのではないでしょうか。

必須ならフレームワークでよろしくやって欲しいのです。

AutoValidateAntiforgeryToken属性

で、よろしくやってくれるのが新たに追加されたAutoValidateAntiforgeryToken属性です。

採用までの経緯は下記に詳しいです。

github.com

これはいったいどういうものかと言うと、「AutoValidateAntiforgeryToken属性が付いた(Post)アクションを利用するフォームでは、ワンタイムトークンをデフォルトで生成する」というものです。

つまり、

public class Some2Controller : Controller
{
  public static readonly string Name = "Some2";

  public IActionResult Index()
  {
    return View();
  }

  [HttpPost]
  [AutoValidateAntiforgeryToken]
  public IActionResult PostAction()
  {
    return View("Index");
  }
}
@using Some2 = PracticeWorld1.Controllers.Some2Controller

@using (Html.BeginForm(nameof(Some2.PostAction),Some2.Name, FormMethod.Post))
{
  <input type="submit" value="Submit"/>
}

<form asp-action="@nameof(Some2.PostAction)" asp-controller="@Some2.Name" method="post" >
  <input type="submit" value="Submit" />
</form>

上記のようにビュー側でのトークン生成のおまじないが必要なくなります。
うっかり忘れた、という事がなくなり開発効率が上がります。

コントローラークラスに付与する

AutoValidateAntiforgeryToken属性はクラスに付与してもいい感じに働きます。
ValidateAntiForgeryToken属性をクラスに付与すると、GETアクションに対してもValidateして使いづらかった記憶があります。

コメントによると

//     An attribute that causes validation of antiforgery tokens for all unsafe HTTP
//     methods. An antiforgery token is required for HTTP methods other than GET, HEAD,
//     OPTIONS, and TRACE.

とあり、GETアクションに対しては何も行わないようです。

下記のようにクラスに付与することで、いちいち全てのPostアクションに対して属性付与する必要がなくなり、開発効率が上がります。

[AutoValidateAntiforgeryToken]
public class Some2Controller : Controller
{
  public static readonly string Name = "Some2";

  public IActionResult Index()
  {
    return View();
  }

  [HttpPost]
  public IActionResult PostAction()
  {
    return View("Index");
  }
}

また、特定のPostアクションではトークンの検証をしたくないと言うときはIgnoreAntiforgeryToken属性を付与することで検証を回避することができます。

終わりに

従来では、@Html.AntiForgeryToken()をうっかり付与し忘れるという凡ミスをよくしていたのでこの機能は便利だと感じました。