ASP.NET SPAサイトにCSRF(XSRF)対策を実装する
ASP.NET
のSPAプロジェクトにクロスサイトリクエストフォージェリ(CSRF, XSRF)対策を実装する方法を紹介します。
前提
SPAのプロジェクトなので、初期表示時(リロード時含む)以外の通信はajax
になります。
ajax
でアクセスするURLはhttps://zukucode.com/api/xxx
のように必ずパスに/api
が付いているものとします。
逆に初期表示時などに使用する通常のURLはhttps://zukucode.com/login
のように/api
はつかないものとします。
XSRFトークン出力
Startup.cs
で、XSRF
トークンを出力するように実装します。
以下のようにXSRF
トークンを出力して、クライアント側にcookieとしてXSRF
トークンがセットされます。
Startup.cs(一部抜粋)
using Microsoft.AspNetCore.Antiforgery;
// 引数にIAntiforgery antiforgeryを追加
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
// 通常アクセス時のみXSRFトークンを出力(ajax時は出力しない)
if (!path.ToLower().Contains("/api"))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() {
HttpOnly = false,
Secure = true
});
}
return next(context);
});
レスポンスヘッダの内容を確認すると、以下のような項目が出力されていることを確認できます。
set-cookie: XSRF-TOKEN=文字列; path=/; secure
CSRF保護
リクエスト時に、上記で出力したXSRF
トークンがリクエストヘッダに含まれていない場合はエラーになるように設定します。
コントローラーでURLごとに設定する方法もありますが、今回はアプリケーション全体でCSRF
保護の設定を行うようにします。
Startup.cs
のConfigureServicesファンクションに以下の処理を追加します。
リクエストヘッダのXSRF
トークンの名前はX-XSRF-TOKEN
とします。
なぜX-XSRF-TOKEN
という名前にするかというと、Angular
やaxios
などのクライアントのajax
ライブラリでは、レスポンスされたXSRF-TOKEN
(cookieのXSRF-TOKEN
)の値を、リクエスト時にX-XSRF-TOKEN
という名前でリクエストヘッダに含める動作を自動的に行っているためです。
トークンの名前を合わせておけば、クライアント側でリクエストヘッダにXSRF
トークンを追加するような処理を実装する必要がなくなります。
Startup.cs(一部抜粋)
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(options =>
{
// リクエストヘッダのXSRFトークンの名前
options.HeaderName = "X-XSRF-TOKEN";
});
services.AddControllersWithViews(options =>
{
// すべてのリクエストに対してCSRF保護を設定
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
動作確認
プロジェクトを作成した際の雛形として作成されたWeatherForecastController
で動作確認を行います。
ここではAPI
としてajax
でアクセスするため、Route
の設定箇所に以下のようにapi
を追加します。
こうすると、api/WeatherForecast
でアクセスできるようになります。
[ApiController]
[Route("[controller]")]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
また、GETリクエストではCSRF保護の動作確認はできないため、POSTリクエストに変更します。
[HttpGet]
public IEnumerable<WeatherForecast> Get()
[HttpPost]
public IEnumerable<WeatherForecast> Post()
クライアント側で、ajaxのリクエストを投げます。
ここではajax
のライブラリにaxios
を使用します。
上述したように、axios
はcookieのXSRF-TOKEN
をX-XSRF-TOKEN
という名前でリクエストヘッダに自動でセットしていますので、リクエストヘッダにトークンを追加する処理は不要です。
const response = axios.post('/api/WeatherForecast');
上記のリクエストのヘッダにX-XSRF-TOKEN: 文字列
が含まれていることを確認でき、データがレスポンスされていることも確認できます。
試しに上記の「XSRFトークン出力」の処理をすべてコメントアウトして試してみます。
また、ブラウザに保存されているXSRF
トークンのcookieは削除しておきます。
その状態でアクセスをすると400(Bad Request)のエラーになることが確認できます。
これによりトークンが存在しないリクエストは拒否されることが確認できます。