Add CSRF protection to .NET core (Antiforgery)

ASP.NET core

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();
            }
        }
}

Post a Comment

Previous Post Next Post