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

yoh*_*hny 5 authentication csrf asp.net-web-api asp.net-core angular

我有一个 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
});
Run Code Online (Sandbox Code Playgroud)

我有服务器端设置来自动检查 CSRF 使用

options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())
Run Code Online (Sandbox Code Playgroud)

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

一开始,angular 应用程序发出 GET 请求以api/init在引导之前获取一些初始数据。在服务器端,此操作按如下方式初始化 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
Run Code Online (Sandbox Code Playgroud)

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

如果之后我api/auth/login从 angular 应用程序登录(包含凭据的 POST 请求),一切正常 - 请求被发布,包括X-XSRF-TOKEN标头和 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" });
}
Run Code Online (Sandbox Code Playgroud)

然而这不起作用。响应包含XSRF-TOKENcookie,但随后的 POST 请求(在我的情况下它的 logout = POST to api/auth/logout)失败并显示 400,尽管 angular 正确地将此 cookie 值放入X-XSRF-TOKEN标头中。我相信原因是.AspNetCore.Antiforgery...由于某种原因未在响应中设置dafault cookie,因此即使在登录后仍保留原始值,因此 CSRF 检查失败,因为值不匹配,

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

yoh*_*hny 6

在做了更多的反复试验并搜索 ASP .NET github 并找到https://github.com/dotnet/aspnetcore/issues/2783https://github.com/aspnet/Antiforgery/issues/155 之后,似乎我想要实现的目标在单个请求中是不可行的,因为用户的身份在单个请求处理期间不会改变(显然是设计使然)。

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

我通过我的登录操作返回 302 重定向来实现这一点,当跟随它时执行 CSRF 刷新。这也需要在注销时完成。

副作用是请求量翻了一番,并且 angular 跟随重定向可能存在问题(在 Chrome 中使用 Angular 9 对我有用,但互联网上有很多关于 angular 的 HttpClient 不跟随重定向的抱怨)。此外,当登录请求成功时,您可能最终处于不确定状态,但是由于某种原因,重定向到令牌刷新失败 - 然后您会遇到过时的 CSRF 令牌。所以不是很好,也不可怕......