The CSRF attack happens when an active session has been established with some website and when a maliciouis site tries to submit bad data on top of it.
The session identifier is stored in a cookie which is automatically being submitted on every HTTP request.
In order to overcome this issue we can mark the session cookie as "Strict" or send some extra data as a cookie and expect it back as a header. The malicious site may submit the cookie but it cannot read it's value.
Here are the basic steps to set up a CSRF protection in ASP.NET core.
Add the anti forgery service to the startup code:
builder.Services.AddAntiforgery((options) => {
options.HeaderName = "X-XSRF-TOKEN";
options.SuppressXFrameOptionsHeader = false;
});
Inject the "IAntiForgery" service in a controller that is called immediately after login
Set the anti forgery cookie in an action called immediately after login:
var tokenSet = antiforgery.GetAndStoreTokens(HttpContext);
Response.Cookies.Append(
"XSRF-TOKEN",
tokenSet.RequestToken!,
new CookieOptions {
HttpOnly = false
});
or in a middleware, right after AddAuthentication or else the token won't include the current identity
app.Use(async (context, next) =>
{
using var scope = context.RequestServices.CreateScope();
var antiforgery = scope.ServiceProvider.GetRequiredService<IAntiforgery>();
if (context.User.Identity != null)
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions
{
HttpOnly = false,
MaxAge = TimeSpan.FromMinutes(5),
});
}
await next();
});
If you name the cookie "XSRF-TOKEN" it will be picked up automatically by various libraries like Axios or Angular.
To validate the anti forgery token DO NOT use [ValidateAntiForgeryToken] attribute, instead check it with the IAntiforgery service:
bool requestValid = await _antiforgery.IsRequestValidAsync(HttpContext);
if (!requestValid)
return BadRequest();
Note, this skips safe HTTP verbs (GET/OPTIONS)
Create a custom action filter with it:
public class AntiForgeryActionFilterAttribute: ActionFilterAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
using var scope = context.HttpContext.RequestServices.CreateScope();
var antiforgery = scope.ServiceProvider.GetService<IAntiforgery>();
bool requestValid = await antiforgery.IsRequestValidAsync(context.HttpContext);
if (!requestValid)
{
context.Result = new BadRequestResult();
}
else
{
await next();
}
}
}