I want to use AntiForgeryTokens
on every HttpPost Action using an ActionFilter that is in a controller named ControllerBase
that every other controller inherits from.
I want to do this by creating an ActionFilter that inherits from ValidateAntiForgeryToken
which takes an argument that tells it what HTTP verbs to apply itself to. I then want to apply that filter on ControllerBase
to ensure that the AntiForgeryToken
is checked for EVERY POST operation on the entire site.
I was looking into using this solution, but
AuthorizationContext Constructor (ControllerContext)
is an obsolete constructor involved and I am not sure how to rebuild the code using the recommended AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
.
It does not appear to use the AntiForgeryToken by default as I get the following error: A required anti-forgery token was not supplied or was invalid
after every post action.
How should I rewrite my ActionFilter to meet current non-obsolete standards and to properly use an anti-forgery token on every [HttpPost]
verb?
Do I have to include an anti-forgery token in every form myself (I am thinking I do)? (as opposed to it being automatically generated - don't laugh, I'm curious) Update: As pointed out in the comments; Yes, this has to be done with every form.
Here is the code from my ControllerBase for reference:
[UseAntiForgeryTokenOnPostByDefault]
public class ControllerBase : Controller
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class BypassAntiForgeryTokenAttribute : ActionFilterAttribute
{
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class UseAntiForgeryTokenOnPostByDefault : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (ShouldValidateAntiForgeryTokenManually(filterContext))
{
var authorizationContext = new AuthorizationContext(filterContext.Controller.ControllerContext);
//Use the authorization of the anti forgery token,
//which can't be inhereted from because it is sealed
new ValidateAntiForgeryTokenAttribute().OnAuthorization(authorizationContext);
}
base.OnActionExecuting(filterContext);
}
/// <summary>
/// We should validate the anti forgery token manually if the following criteria are met:
/// 1. The http method must be POST
/// 2. There is not an existing [ValidateAntiForgeryToken] attribute on the action
/// 3. There is no [BypassAntiForgeryToken] attribute on the action
/// </summary>
private static bool ShouldValidateAntiForgeryTokenManually(ActionExecutingContext filterContext)
{
var httpMethod = filterContext.HttpContext.Request.HttpMethod;
//1. The http method must be POST
if (httpMethod != "POST") return false;
// 2. There is not an existing anti forgery token attribute on the action
var antiForgeryAttributes =
filterContext.ActionDescriptor.GetCustomAttributes(typeof (ValidateAntiForgeryTokenAttribute), false);
if (antiForgeryAttributes.Length > 0) return false;
// 3. There is no [BypassAntiForgeryToken] attribute on the action
var ignoreAntiForgeryAttributes =
filterContext.ActionDescriptor.GetCustomAttributes(typeof (BypassAntiForgeryTokenAttribute), false);
if (ignoreAntiForgeryAttributes.Length > 0) return false;
return true;
}
}
}
I've used the following approach:
public class SkipCSRFCheckAttribute : Attribute
{
}
public class AntiForgeryTokenFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (IsHttpPostRequest(filterContext) && !SkipCsrfCheck(filterContext))
AntiForgery.Validate();
}
private static bool IsHttpPostRequest(AuthorizationContext filterContext)
{
return filterContext.RequestContext.HttpContext.Request.HttpMethod == HttpMethod.Post.ToString();
}
private static bool SkipCsrfCheck(AuthorizationContext filterContext)
{
return filterContext.ActionDescriptor.GetCustomAttributes(typeof (SkipCSRFCheck), false).Any();
}
}
Which enables us to disable it on a case-by-case basis using the SkipCSRFCheck attribute, and then registering it as a global filter in the Application_Start:
GlobalFilters.Filters.Add(new AntiForgeryTokenFilter());
You don't need to instantiate any AuthorizationContext
or call the OnAuthorization
method, simply:
if (ShouldValidateAntiForgeryTokenManually(filterContext))
{
AntiForgery.Validate(filterContext.HttpContext, null);
}
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