単一ページのASP.NET MVCコアアプリケーションでも同じ問題が発生しました。HttpContext.User現在のIDクレームを変更するすべてのコントローラーアクションを設定することで、これを解決しました(ここで説明するように、MVCは後続の要求に対してのみこれを行うため)。ミドルウェアの代わりに結果フィルターを使用して、偽造防止Cookieを応答に追加しました。これにより、MVCアクションが返された後でのみ生成されることが確認されました。
コントローラー(注:ASP.NET Core Identityでユーザーを管理しています):
[Authorize]
[ValidateAntiForgeryToken]
public class AccountController : Controller
{
    private SignInManager<IdentityUser> signInManager;
    private UserManager<IdentityUser> userManager;
    private IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory;
    public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
    {
        this.signInManager = signInManager;
        this.userManager = userManager;
        this.userClaimsPrincipalFactory = userClaimsPrincipalFactory;
    }
    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Login(string username, string password)
    {
        if (username == null || password == null)
        {
            return BadRequest(); // Alias of 400 response
        }
        var result = await signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            var user = await userManager.FindByNameAsync(username);
            // Must manually set the HttpContext user claims to those of the logged
            // in user. Otherwise MVC will still include a XSRF token for the "null"
            // user and token validation will fail. (MVC appends the correct token for
            // all subsequent reponses but this isn't good enough for a single page
            // app.)
            var principal = await userClaimsPrincipalFactory.CreateAsync(user);
            HttpContext.User = principal;
            return Json(new { username = user.UserName });
        }
        else
        {
            return Unauthorized();
        }
    }
    [HttpPost]
    public async Task<IActionResult> Logout()
    {
        await signInManager.SignOutAsync();
        // Removing identity claims manually from the HttpContext (same reason
        // as why we add them manually in the "login" action).
        HttpContext.User = null;
        return Json(new { result = "success" });
    }
}
偽造防止Cookieを追加する結果フィルター:
public class XSRFCookieFilter : IResultFilter
{
    IAntiforgery antiforgery;
    public XSRFCookieFilter(IAntiforgery antiforgery)
    {
        this.antiforgery = antiforgery;
    }
    public void OnResultExecuting(ResultExecutingContext context)
    {
        var HttpContext = context.HttpContext;
        AntiforgeryTokenSet tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
        HttpContext.Response.Cookies.Append(
            "MyXSRFFieldTokenCookieName",
            tokenSet.RequestToken,
            new CookieOptions() {
                // Cookie needs to be accessible to Javascript so we
                // can append it to request headers in the browser
                HttpOnly = false
            } 
        );
    }
    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}
Startup.cs抽出:
public partial class Startup
{
    public Startup(IHostingEnvironment env)
    {
        //...
    }
    public IConfigurationRoot Configuration { get; }
    public void ConfigureServices(IServiceCollection services)
    {
        //...
        services.AddAntiforgery(options =>
        {
            options.HeaderName = "MyXSRFFieldTokenHeaderName";
        });
        services.AddMvc(options =>
        {
            options.Filters.Add(typeof(XSRFCookieFilter));
        });
        services.AddScoped<XSRFCookieFilter>();
        //...
    }
    public void Configure(
        IApplicationBuilder app,
        IHostingEnvironment env,
        ILoggerFactory loggerFactory)
    {
        //...
    }
}