Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ValidateAntiForgeryToken in an ASP.NET Core React SPA Application

I'm trying to use the framework's tools to add some simple CSRF validation to an ASP.NET Core React SPA. The application itself is essentially a create-react-app setup (a single index.html with a root element and everything else is loaded in from bundled JavaScript).

Tinkering with some information found on links such as this one, I've set the following in my Startup.ConfigureServices:

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

And confirmed in my Chrome tools that the cookie is being set. If I omit the above line, a cookie is still set with a partially randomized name, such as: .AspNetCore.Antiforgery.RAtR0X9F8_w Either way the cookie is being set. I've also confirmed that any time I re-start the whole application the cookie value is updated, so the framework is actively setting this cookie.

Observing network requests in my Chrome tools, I confirm that the cookie is being sent to the server on AJAX request. Placing a breakpoint on the server and observing the Request.Cookies value in a controller action also confirms this.

However, if I decorate any such AJAX requested action with [ValidateAntiForgeryToken] then the response is always an empty 400.

Is there a configuration step I've missed somewhere? Perhaps the action attribute is looking in the wrong place and I need to use a different validation?

like image 944
David Avatar asked Nov 26 '18 19:11

David


1 Answers

I just inspect the log and find out there's an exception:

Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery cookie ".AspNetCore.Antiforgery.HPE6W9qucDc" is not present. at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)

It indicates that you forgot to configure the cookie name :

   public void ConfigureServices(IServiceCollection services)
   {
       //services.AddAntiforgery();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

       // In production, the React files will be served from this directory
       services.AddSpaStaticFiles(configuration =>
       {
           configuration.RootPath = "ClientApp/build";
       });
   }

So I just add a configuration as below :

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAntiforgery(o => {
            o.Cookie.Name = "X-CSRF-TOKEN";
        });
        // ...
    }

and it works now.

Also, if you would like to omit the line of services.AddAntiforgery(options => options.Cookie.Name = "X-CSRF-TOKEN"); , you can use the built-in antiforgery.GetAndStoreTokens(context) method to send cookie:

   app.Use(next => context =>
    {
        if (context.Request.Path == "/")
        {
            //var tokens = antiforgery.GetTokens(context);
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("X-CSRF-TOKEN", tokens.CookieToken, new CookieOptions { HttpOnly = false });
            context.Response.Cookies.Append("X-CSRF-FORM-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
        }
        return next(context);
    })

Both should work as expected.

like image 159
itminus Avatar answered Nov 10 '22 00:11

itminus