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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With