在 ASP .NET Core Web API 中使用没有身份的 cookie 身份验证时如何在登录时刷新 CSRF 令牌

How to refresh CSRF token on login when using cookie authentication without identity in ASP .NET Core Web API

我有一个 ASP .NET Core 3.1 后端,带有 angular 9 前端(基于 dotnet angular 模板,只是更新了 angular 到 v9)。 我使用 cookie 身份验证(我知道 JWT 更适合 SPA,以此作为实验)并且我还在服务器端添加了对 CSRF 保护的支持:

services.AddAntiforgery(options =>
{
   options.HeaderName = "X-XSRF-TOKEN"; // angular csrf header name
});

我有服务器端设置,使用

自动检查 CSRF
options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())

因此 GET 请求不会根据 CSRF 检查,但 POST 会检查。

一开始,angular 应用程序向 api/init 发出 GET 请求,以在引导之前获取一些初始数据。在 server-side 上,此操作按如下方式初始化 CSRF:

// init action body
var tokens = _antiForgery.GetAndStoreTokens(HttpContext);
Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions
{
   HttpOnly = false
});
// return some inital data DTO

这按预期工作 - GET 响应包含 2 个 CSRF cookie - 第一个是 ASP .NET 核心默认 CSRF cookie .AspNetCore.Antiforgery...,第二个是 XSRF-TOKEN angular将读取并放入 X-XSRF-TOKEN header 以供后续请求使用。

如果之后我从 angular 应用程序登录(POST 请求包含 api/auth/login 的凭据),一切正常 - 请求是 POSTed 包括 X-XSRF-TOKEN header 并且 CSRF 验证通过,因此如果凭据正确,则用户已登录。

现在问题开始了。 ASP .NET 服务器应用程序使用没有身份的 cookie 身份验证,如此处所述 https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1。在登录操作中,还需要重新生成 CSRF 令牌,因为随着身份验证,CSRF 令牌开始包括经过身份验证的用户身份。因此,我的登录操作如下所示:

public async Task<IActionResult> Login(CredentialsDto credentials)
{
   // fake user credentials check
   if (credentials.Login != "admin" || credentials.Password != "admin")
   {
      return Unauthorized();
   }

   var claimsIdentity = new ClaimsIdentity(new[]
   {
     new Claim(ClaimTypes.Name, "theAdmin"),
   }, CookieAuthenticationDefaults.AuthenticationScheme);

   var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
   await HttpContext.SignInAsync(claimsPrincipal); 

   // refresh antiforgery token on login (same code as in init action before)
   var tokens = _antiForgery.GetAndStoreTokens(HttpContext);
   Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions 
   {
       HttpOnly = false
   });

   return new JsonResult(new UserDto { Id = 1, Login = "theAdmin" });
}

但这不起作用。响应包含 XSRF-TOKEN cookie,但随后的 POST 请求(在我的例子中,它的注销 = POST 到 api/auth/logout)失败并显示 400,尽管 angular 正确放置这个cookie值变成X-XSRF-TOKENheader。 我相信原因是由于某种原因没有在响应中设置 dafault .AspNetCore.Antiforgery... cookie,因此即使在登录后仍保留原始值,因此 CSRF 检查失败,因为值不匹配,

在这种情况下如何正确刷新 CSRF 令牌?

在进行了更多尝试和错误并搜索 ASP .NET github 并找到 https://github.com/dotnet/aspnetcore/issues/2783 and https://github.com/aspnet/Antiforgery/issues/155 之后,看来我想要实现的目标无法在单个请求中实现,因为用户的身份在单个请求处理期间不会改变(显然是设计使然)。

让它工作的唯一方法(也就是在 login/logout 之后刷新 CSRF 令牌)是在之后发出额外的请求,其中 CSRF 令牌将根据新获得的身份正确刷新。

我通过返回 302 重定向的登录操作实现了这一点,随后执行 CSRF 刷新。注销也需要这样做。

副作用是请求数量翻了一番,angular 重定向后可能会出现问题(在 Chrome 中使用 Angular 9 为我工作,但有很多互联网上关于 angular 的 HttpClient 不遵循重定向的抱怨)。此外,当登录请求成功时,您可能会陷入困境,但由于某种原因重定向到令牌刷新失败 - 然后您就会陷入过时的 CSRF 令牌状态。所以不是很好,也不是很糟糕...