Apparently Random Error: "Antiforgery token validation failed. The antiforgery cookie token and request token do not match."
Asked Answered
S

5

9

Background

I have a relatively new ASP.NET Core 2 site. It's running on just one server (Windows Server 2012 R2, IIS 8.5), and I only restart the site once every few days when I upload an update. About once a day, a user's request fails due to rejection by the anti-forgery system. These are POST requests, and there's nothing particularly special about them. I'm including the anti-forgery value in the POST request, and 99% of the time, POST requests work. But when they don't, the stdout log says, "Antiforgery token validation failed. The antiforgery cookie token and request token do not match." When I perform a Web search using that exact statement, I get zero results. So I've turned to Stack Overflow. [This is no longer true as a Web search now yields this Stack Overflow question.]

Errors

I've included the relevant portions of the stdout log below.

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST [domain redacted] application/x-www-form-urlencoded 234
info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter[1]
      Antiforgery token validation failed. The antiforgery cookie token and request token do not match.
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery cookie token and request token do not match.
   at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
   at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.<ValidateRequestAsync>d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter.<OnAuthorizationAsync>d__3.MoveNext()
info: Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker[3]
      Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.AutoValidateAntiforgeryTokenAuthorizationFilter'.
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
      Executing HttpStatusCodeResult, setting HTTP status code 400
info: Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker[2]
      Executed action /Index in 2.6224ms
warn: Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery[1]
      Antiforgery validation failed with message 'The antiforgery cookie token and request token do not match.'.

For requests that result in the above stdout output, IAntiforgery.IsRequestValidAsync agrees by returning false. Notice the error message "The antiforgery cookie token and request token do not match." Here's a reduced example of a failed POST request and the associated cookie.

POST: __RequestVerificationToken= CfDJ8F9Fs4CqDFpLttT96eZw9WHjWfHO8Yawn35k4Yq3gDK5n1TDJDDiY5o86VQs1_qOVIYBydCizBU4knb7Jmq1-heGhwnMu2KmhUIiAd0xI7Sudv3GX-J0OI6wRfiPL4L1KRs2Pml8dbsDfwemewBqi18

Cookie: .AspNetCore.Antiforgery.ClRyCRmWApY=CfDJ8F9Fs4CqDFpLttT96eZw9WFtJht41WcNrmgshi2pFGwcxhr0_0hvINQc7Yl9Cbjhv-TiSNXeEctyKborLI49AcjHfWIgOmmKkbjOe7QMn8Z0WZtkQy5JcaBHKEGTu1p-La8JL8pZZqZy02Hrswpkh3I

I've also captured this data a few times after the request has failed with a 400 error (using some error handling middleware):

AntiforgeryTokenSet tokens = antiforgery.GetTokens(context);
tokens.CookieToken:  null
tokens.FormFieldName:  "__RequestVerificationToken"
tokens.HeaderName:  "RequestVerificationToken"
tokens.RequestToken:  "CfDJ8F9Fs4CqDFpLttT96eZw9WH33jSw5mM8h7RpEd3vGISQTRkx1rfwm-L2lfkvXKMBc-riESmoTo_fnIjeBbRmOo5KuJHr09f8B75sQ9g_djIVeeaGwMw5KE6W1O2-7Vi03fCnwlTv8l-BWGst76Ln-ZQ"

So here are the three strings:

POST String:  "CfDJ8F9Fs4CqDFpLttT96eZw9WHjWfHO8Yawn35k4Yq3gDK5n1TDJDDiY5o86VQs1_qOVIYBydCizBU4knb7Jmq1-heGhwnMu2KmhUIiAd0xI7Sudv3GX-J0OI6wRfiPL4L1KRs2Pml8dbsDfwemewBqi18"
Cookie String:  "CfDJ8F9Fs4CqDFpLttT96eZw9WFtJht41WcNrmgshi2pFGwcxhr0_0hvINQc7Yl9Cbjhv-TiSNXeEctyKborLI49AcjHfWIgOmmKkbjOe7QMn8Z0WZtkQy5JcaBHKEGTu1p-La8JL8pZZqZy02Hrswpkh3I"
antiforgery.GetTokens(context).RequestToken:  "CfDJ8F9Fs4CqDFpLttT96eZw9WH33jSw5mM8h7RpEd3vGISQTRkx1rfwm-L2lfkvXKMBc-riESmoTo_fnIjeBbRmOo5KuJHr09f8B75sQ9g_djIVeeaGwMw5KE6W1O2-7Vi03fCnwlTv8l-BWGst76Ln-ZQ"

The POST string and cookie string don't match, but in my experience, even with requests ASP.NET Core considers legitimate, they never do. But strangely, the POST string and tokens.RequestToken don't match either. I would think they should match, although I captured tokens.RequestToken later in the request lifecycle, so maybe that has something to do with it.

ASP.NET Core 2 on GitHub

I decided to look at the source code of ASP.NET Core 2. I found this file, especially line 145:

https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs

That line gets the message "The antiforgery cookie token and request token do not match." from this file at line 134:

https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Resources.resx

So I think that's where the message is originating, but I'm still left wondering why this is happening.

Question

Would someone please help me figure out why these anti-forgery tokens aren't validating? Is it possible the user's Web browser is mangling the cookie or POST data? Does anyone have experience in this area or any suggestions? Thank you.

Scop answered 10/1, 2018 at 3:8 Comment(10)
What is the timeout on that cookie? Maybe the user is just waiting to long? People tend to keep pages open for hours. Could also be some automatic is cleaning up the "valid anti-forgery token" in the Database at the worst time.Medellin
Thank you, @Christopher. I don't know the timeout of the cookie. It's set by ASP.NET Core. But the cookie is present. It's just not satisfactory to the anti-forgery system. Also, I can see cases when users submit the form seconds after the page loading. So I don't think it's related to timeout. I don't know what cleaning you're talking about in the database. I'm storing them in memory (ASP.NET Core's default, as far as I can tell). Since the site isn't restarting, the stored values should still be intact.Scop
@Scop I'm seeing something very similar. Did you ever figure out what the problem is?Leeuwenhoek
@GaryBrunton, no. I'm still struggling with it. I posted it to the ASP.NET Core GitHub issues page a few days ago, but that hasn't gotten me anywhere yet: github.com/aspnet/Home/issues/2882.Scop
@Scop We were able to track our issue down. We didn't know that the antiforgery token considers if a user is authenticated. So in our case, we found that an authenticated user would open multiple browser tabs that contained a form (and antiforgery token). Eventually they would log out from one of the tags. After logout, if they were to go back to one of the other tabs that contains the form and try to submit it, this error would be thrown because the token was generated with the assumption that the user is authenticated. I'm not sure if this helps you.Leeuwenhoek
@GaryBrunton, it does not because I'm not using ASP.NET's authentication system. But thank you anyway.Scop
I see we maybe have the same problem, and I see I also replied on the GitHub issue. Do you have any manually called @Html.AntiForgeryToken() calls on the page (for Ajax requests etc.)?Cartagena
@KoalaBear, I do not call it directly, no. I think it's probable my call to using (Html.BeginForm(FormMethod.Post)) { } might call it. Still, other than the error message, I'm not sure your problem on GitHub is the same as mine.Scop
OK, indeed, having multiple forms will probably also call it underwater. It might be a different problem.Cartagena
I know this is an old issue but did you manage to resolve it somehow guys? At the moment I am struggling with it using .NET 5. @KoalaBear, I saw you asked if someone is using @Html.AntiForgeryToken() manually. Do you have in mind, because you have asked the same thing in the github ussue, too. Asking because I am using it manually.Gimp
T
4

Disabling the filter globally seems to be the only way to turn it off. I got @svallis's code to work with a sight modification:

services.AddMvc().AddRazorPagesOptions(options =>
{
    options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
});

https://github.com/aspnet/Mvc/issues/7012

Titfer answered 22/1, 2018 at 22:52 Comment(3)
Thank you, but this doesn't satisfy my requirement. I want to use the anti-forgery system, not disable it.Scop
@jaybro, I didn't resolve it. I opened the issue with Microsoft on GitHub months ago. They haven't given a meaningful response yet, and the issue remains open.Scop
@Scop ok, thx, I discovered my issue was related to two forms on the same view using the token, I disabled one of the forms via the asp-antiforgery attribute and the other (important) form started working.Okajima
H
0

I found solution here: https://github.com/aspnet/Antiforgery/issues/116

using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;


// fix from https://github.com/aspnet/Antiforgery/issues/116
namespace WebAppCore.Code
{
    public class HtmlGeneratorNoStoreAntiforgery: DefaultHtmlGenerator  
    {
        public HtmlGeneratorNoStoreAntiforgery(
            IAntiforgery antiforgery,
            IOptions<MvcViewOptions> optionsAccessor,
            IModelMetadataProvider metadataProvider,
            IUrlHelperFactory urlHelperFactory,
            HtmlEncoder htmlEncoder,
            ValidationHtmlAttributeProvider validationAttributeProvider)
            : base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider)
        {
        }

        public override IHtmlContent GenerateAntiforgery(ViewContext viewContext)
        {
            var result = base.GenerateAntiforgery(viewContext);

            viewContext
                    .HttpContext
                    .Response
                    .Headers[HeaderNames.CacheControl]
                = "no-cache, max-age=0, must-revalidate, no-store";

            return result;
        }
    }
}

and add in startup.cs:

services.AddSingleton<Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator, HtmlGeneratorNoStoreAntiforgery>();
Housetop answered 9/7, 2020 at 8:58 Comment(0)
M
0

Regarding CookieToken to be null: Maybe that request was forged? Since the cookie is missing and I suspect that your website DOES send it every request. When can it be missing? When it's coming from somewhere else.

Regarding the other issue:

  1. Is your middlewhere, where the AntiforgeryToken is added as a cookie on the response after the authentication part of the Configure method?
  2. Is it possible that it might be that you're returning several AntiforgeryTokens on several GET-requests? In this case there might be a race condition which request comes back the latest (what the browser will use) and which request left the server the last; these might be different -> token mismatch.

You should return just one, at the first Get operation. When you're hosting the Frontend and Backend on one instance it will probably be the root (/) and if you're using an API on another port you should make sure that you do a GET before a POST, PUT or PATCH.

One instance startup example:

builder.Use(next => context =>
{
    var path = context.Request.Path.Value;
    if (!string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
        context.Request.Method != "GET") return next(context);
    var tokens = antiforgery.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
        new CookieOptions { HttpOnly = false, Path = "/" });

    return next(context);
});

Multiple ports instance (mind the CORS)

public IServiceProvider ConfigureServices(IServiceCollection services)
{
  services.AddCors(options =>
  {
     options.AddDefaultPolicy(builder => builder
            .WithOrigins(/*allowed domains here*/)
            .AllowAnyHeader()
            .WithMethods("GET", "POST", "PUT", "DELETE")
            .AllowCredentials()
            .Build()
     );
  });
}
public virtual void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
  app.UseAuthentication()
     .UseAuthorization()
     .Use(next => context =>
      {
         var path = context.Request.Path.Value;
         if (!string.Equals(path, "/api/settings", StringComparison.OrdinalIgnoreCase) ||
             context.Request.Method != "GET") return next(context);
         var tokens = antiforgery.GetAndStoreTokens(context);
         context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
             new CookieOptions { HttpOnly = false });

         return next(context);
      });
}

Bear in mind that if you're hosting the Frontend on another port/location you should add the header for withCredentials like this:

return this.http.get<Settings>(`/api/settings`, { withCredentials: true });
Mess answered 19/11, 2021 at 9:41 Comment(0)
A
0

I was looking for a solution on a new .NET 6.0 Blazor Server App with Authentification and fixit like the MSDN without disable Antiforgery:

 app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});
Animalist answered 5/5, 2022 at 10:17 Comment(0)
N
0

For me the problem was that I just forgot to send the header X-XSRF-TOKEN.
To validation works, we need the cookies and the header together.

Nilla answered 17/7, 2024 at 16:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.