Jon
Jon

Reputation: 40062

Validation of the provided antiforgery token failed. The cookie token and the request token were swapped

I have create a ASP.Net Core 2 application using IAntiforgery apis.

This provides a method to return a cookie which it does.

The client takes that cookie and on subsequent POST requests it puts the value in a X-XSRF-TOKEN header.

Middleware validates this and allows the request to continue or not if it fails.

With the correct cookies and headers sent in the request always fails validation and I don't understand why.

The whole reproduction is here https://github.com/jchannon/AntiForgery

However, the main problem area is below.

public class Startup
{
    public void Configure(IApplicationBuilder app, IAntiforgery antiforgery, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication();

        app.Use(async (context, next) =>
        {
            var logger = loggerFactory.CreateLogger("ValidRequestMW");

            //Don't validate POST for login
            if (context.Request.Path.Value.Contains("login"))
            {
                await next();
                return;
            }

            logger.LogInformation(context.Request.Cookies["XSRF-TOKEN"]);
            logger.LogInformation(context.Request.Headers["X-XSRF-TOKEN"]);

            //On POST requests it will validate the XSRF header
            if (!await antiforgery.IsRequestValidAsync(context))
            {

                /****************************************************
                 *
                 *
                 * For some reason when the cookie and the header are sent in on the /create POST this validation always fails
                 * 
                 * 
                 ***************************************************/
                context.Response.StatusCode = 401;

                logger.LogError("INVALID XSRF TOKEN");
                return;
            }
            await next();
        });

        app.UseRouter(r =>
        {
            r.MapGet("", async context => { await context.Response.WriteAsync("hello world"); });

            //This returns a XSRF-TOKEN cookie
            //Client will take this value and add it as a X-XSRF-TOKEN header and POST to /create
            r.MapPost("login", async (context) =>
            {
                antiforgery.SetCookieTokenAndHeader(context);
                context.Response.Redirect("/");
            });

            //If XSRF validaiton is correct we should hit this route
            r.MapPost("create", async context =>
            {
                context.Response.StatusCode = 201;
                await context.Response.WriteAsync("Created");
            });
        });
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLogging(x => x.AddConsole());

        services.AddAntiforgery(options =>
        {
            options.HeaderName = "X-XSRF-TOKEN";
            options.Cookie.Name = "XSRF-TOKEN";
            options.Cookie.HttpOnly = false;
        });

//        services.AddAuthentication("MyCookieMW")
//                .AddCookie("MyCookieMW", cookieOptions =>
//                {
//                    cookieOptions.Cookie.Name = "MyCookie";
//                    cookieOptions.Cookie.HttpOnly = true;
//                    cookieOptions.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
//                    cookieOptions.SlidingExpiration = true;
//                });

        services.AddRouting();
    }
}

Upvotes: 9

Views: 3916

Answers (1)

Jon
Jon

Reputation: 40062

So after diving into the source code of antiforgery and some badly named methods (SetCookieTokenAndHeader I'm looking at you). The correct code should be:

    public void Configure(IApplicationBuilder app, IAntiforgery antiforgery, ILoggerFactory loggerFactory)
    {
        app.Use(async (context, next) =>
        {
            var logger = loggerFactory.CreateLogger("ValidRequestMW");

            //Don't validate POST for login
            if (context.Request.Path.Value.Contains("login"))
            {
                await next();
                return;
            }

            logger.LogInformation("Request Cookie is " + context.Request.Cookies["XSRF-TOKEN"]);
            logger.LogInformation("Request Header is " + context.Request.Headers["X-XSRF-TOKEN"]);

            //On POST requests it will validate the XSRF header
            if (!await antiforgery.IsRequestValidAsync(context))
            {
                context.Response.StatusCode = 401;

                logger.LogError("INVALID XSRF TOKEN");
                return;
            }
            await next();
        });

        app.UseRouter(r =>
        {
            r.MapGet("", async context => { await context.Response.WriteAsync("hello world"); });

            //This returns a XSRF-TOKEN cookie
            //Client will take this value and add it as a X-XSRF-TOKEN header and POST to /create
            r.MapPost("login", async (context) =>
            {
                var tokens = antiforgery.GetAndStoreTokens(context);
                context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                    new CookieOptions() { HttpOnly = false });
                context.Response.Redirect("/");
            });

            //If XSRF validaiton is correct we should hit this route
            r.MapPost("create", async context =>
            {
                context.Response.StatusCode = 201;
                await context.Response.WriteAsync("Created");
            });
        });
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IAntiforgeryTokenGenerator, MyTokenGenerator>();
        services.AddSingleton<IAntiforgery, MyAntiforgery>();

        services.AddLogging(x => x.AddConsole());

        services.AddAntiforgery(options =>
        {
            options.HeaderName = "X-XSRF-TOKEN";
            options.Cookie.Name = "MyAntiforgery";
            options.Cookie.HttpOnly = false;
        });

        services.AddRouting();
    }

Upvotes: 6

Related Questions