Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AntiforgeryValidationException: The provided anti forgery token was meant for a different claims-based user than the current user

I'm not sure what's going on here, but I'm seeing 2 different tokens in my cookie. One is "XSRF-TOKEN", and the other is ".AspNetCore.Antiforgery.OnvOIX6Mzn8", and they have different values.

I'm using ASP.Net Core 2.1, with SPA set (and Angular on the front end) and I have the following in Startup.cs.

I don't know what's creating that latter token, as it doesn't seem to be from any of the code I added.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAntiforgery antiforgery)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(
            builder =>
            {
            builder.Run(
                async context =>
                {
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                context.Response.Headers.Add("Access-Control-Allow-Origin", "*");

                var error = context.Features.Get<IExceptionHandlerFeature>();
                if (error != null)
                {
                    context.Response.AddApplicationError(error.Error.Message);
                    await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false);
                }
                });
            });
    }

    app.UseAuthentication();
    app.UseHttpsRedirection();
    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseJwtTokenMiddleware();
    app.UseSpaStaticFiles();
    app.UseCookiePolicy();

    app.UseMvc(routes =>
    {
            routes.MapRoute(
                name: "default",
                template: "{controller}/{action=Index}/{id?}");
        });

        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "ClientApp";
            spa.UseSpaPrerendering(options =>
            {
                options.BootModulePath = $"{spa.Options.SourcePath}/dist/server/main.js";
                options.BootModuleBuilder = env.IsDevelopment()
                    ? new AngularCliBuilder(npmScript: "build:ssr")
                    : null;
                options.ExcludeUrls = new[] { "/sockjs-node" };
            });

            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });

        app.UseMiddleware<AntiForgeryMiddleware>("XSRF-TOKEN");
    }
}

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseAntiforgeryTokenMiddleware(this IApplicationBuilder builder, string requestTokenCookieName)
    {
        return builder.UseMiddleware<AntiForgeryMiddleware>(requestTokenCookieName);
    }
}

public class AntiForgeryMiddleware
{
    private readonly RequestDelegate next;
    private readonly string requestTokenCookieName;
    private readonly string[] httpVerbs = new string[] { "GET", "HEAD", "OPTIONS", "TRACE" };

public AntiForgeryMiddleware(RequestDelegate next, string requestTokenCookieName)
{
    this.next = next;
    this.requestTokenCookieName = requestTokenCookieName;
}

public async Task Invoke(HttpContext context, IAntiforgery antiforgery)
{
    if (httpVerbs.Contains(context.Request.Method, StringComparer.OrdinalIgnoreCase))
    {
        var tokens = antiforgery.GetAndStoreTokens(context);

        context.Response.Cookies.Append(requestTokenCookieName, tokens.RequestToken, new CookieOptions()
        {
            HttpOnly = false
        });
    }

    await next.Invoke(context);
}

}

like image 480
Jonas Arcangel Avatar asked Jul 12 '18 18:07

Jonas Arcangel


2 Answers

The .AspNetCore.Antiforgery.OnvOIX6Mzn8 cookie in your example is being generated by the call to GetAndStoreTokens. This call generates two tokens:

  1. The "Cookie Token": This is what gets written as .AspNetCore.Antiforgery.OnvOIX6Mzn8.
  2. The "Request Token": This is a separate token that gets paired with the "Cookie Token". You write this value out as a cookie yourself (namely XSRF-TOKEN) as is shown in your code.

You can see this in your example, if you examine the value of the generated value tokens. This has two properties of interest: RequestToken and CookieToken. The GetAndStoreTokens call is writing out the CookieToken value and your code is writing out the RequestToken value, which explains why you are seeing two different values in the two different cookies.

The code you have for this appears to come directly from the docs, which explains that:

...uses middleware from the app's home page to generate an antiforgery token and send it in the response as a cookie (using the default Angular naming convention described later in this topic)...

When sending a request to the server that needs to be validated according to the antiforgery rules, both the cookie and a corresponding header are required. The ASP.NET Core Antiforgery system matches the "Request Token" and the "Cookie Token" together during the validation process.

Further down in the docs, you'll see:

...using local storage to store the antiforgery token on the client and sending the token as a request header is a recommended approach.

The JavaScript example that follows shows how the RequestToken value is added to the XHR request using a custom request header (namely RequestVerificationToken). The docs also show that this header can be changed when registering the Antiforgery services in the ASP.NET Core DI system:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");

You need to read the value of the XSRF-TOKEN cookie in your Angular application and send it back to the server with your API requests. You can use the default header name (as I mentioned above) or customise it (also mentioned above). AS @Sal has pointed out in his answer, AngularJs has a built-in mechanism for this so long as the cookie name is XSRF-TOKEN (this is also the same for Angular).

Hopefully, that explains the two tokens and the general Antiforgery process. However, from the fact that you're writing out a correctly named cookie for Angular's default HttpClient XSRF protection, I expect that all of this side of things is configured correctly.

In terms of where your problem might lie, I'm suspicious of the location of this line:

app.UseMiddleware<AntiForgeryMiddleware>("XSRF-TOKEN");

Given that this is the last call in your pipeline setup code, it will only run after the MVC pipeline and only in cases where the MVC pipeline does not handle the request. To address this, move the call above UseMvc - This might be a bit aggressive in that it will generate new "Request Token"s more than will be necessary, but it will confirm whether or not that's your issue.

like image 185
Kirk Larkin Avatar answered Nov 15 '22 06:11

Kirk Larkin


By default AspNetCore creates a cookie with the "AspNetCore.AntiForgery.XXX" (unless renamed by your configuration) to prevent xsrf/csrf attacks.

I've also read in this MSDN article that AngularJS also has an automatic way of handling xsrf/csrf scenarios:

AngularJS uses a convention to address CSRF. If the server sends a cookie with the name XSRF-TOKEN, the AngularJS $http service adds the cookie value to a header when it sends a request to the server. This process is automatic. The header doesn't need to be set explicitly. The header name is X-XSRF-TOKEN. The server should detect this header and validate its contents. For ASP.NET Core API work with this convention: Configure your app to provide a token in a cookie called XSRF-TOKEN. Configure the antiforgery service to look for a header named X-XSRF-TOKEN.

services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

Could it be that your application is configured to generate both the default AntiForgery cookie and the XSRF-TOKEN? If that were the case you might be generating and receiving two different anti forgery tokens which might be causing the problem

like image 22
Sal Avatar answered Nov 15 '22 06:11

Sal