Using AntiForgeryToken with other verbs than POST (and ActionLinks)
Today I have found this question on forums.asp.net and decided to take a look at it. I must warn you, the solution might be considered a little bit hacky.
So for starters, we have an ActionLink which looks like this:
So for starters, we have an ActionLink which looks like this:
<%= Ajax.ActionLink("Delete", "AjaxDeleteItem", new { ItemId = item.ItemId }, new AjaxOptions { Confirm = "Are you sure you want to delete the item?", HttpMethod = "Delete", UpdateTargetId = "divList"}) %>and we want to secure this Delete action with AntiForgeryToken. First we need to add AntiForgeryToken helper to the view:
<%= Html.AntiForgeryToken() %>Now let's modify our ActionLink:
<%= Ajax.ActionLink("Delete", "AjaxDeleteItem", new { ItemId = item.ItemId, __RequestVerificationToken = "_" }, new AjaxOptions { Confirm = "Are you sure you want to delete the item?", HttpMethod = "Delete", UpdateTargetId = "divList" })%>Here comes the hacky part. We will use this additional route value (__RequestVerificationToken = "_") as a placeholder to inject the value of our AntiForgeryToken into links href with a little help of jQuery:
$(document).ready(function () {Our job on client side is done, all the links which had our placeholder in their href now have AntiForgeryToken. We should now add token verification to the controller action:
//Finding AntiForgeryToken input
var antiForgeryToken = $('input[name=__RequestVerificationToken]');
if (antiForgeryToken.length > 0) {
//Serializing AntiForgeryToken
var antiForgeryTokenSerialized = antiForgeryToken.serialize();
//For each anchor in page
$('a').each(function (index, element) {
//Replace placeholder with serialized AntiForgeryToken
$(element).attr('href', $(element).attr('href').replace('__RequestVerificationToken=_', antiForgeryTokenSerialized));
});
}
});
[AcceptVerbs(HttpVerbs.Delete)]If you would run the code now, you would see that it's not working. That's because AntiForgeryToken is designed for POST requests and it expects the token in Request.Form collection. To work around this we will modify the following line of OnAuthorization method in ValidateAntiForgeryTokenAttribute class:
[ValidateAntiForgeryToken]
public ActionResult AjaxDeleteItem(string ItemId)
{
...
}
string formValue = filterContext.HttpContext.Request.Form[fieldName];to this:
HttpVerbs verb;After that small change, this workaround works the way we wanted to. The code was tested only in few simple testcases, so it migth contain some flaws.
//Parse HttpVerb from Request.GetHttpMethodOverride()
if (!Enum.TryParse<HttpVerbs>(filterContext.HttpContext.Request.GetHttpMethodOverride(), true, out verb))
//If method couldn't be parsed, assume POST
verb = HttpVerbs.Post;
//Extract token based on HttpVerb
string formValue = String.Empty;
if (verb == HttpVerbs.Post || verb == HttpVerbs.Put)
formValue = filterContext.HttpContext.Request.Form[fieldName];
else
formValue = filterContext.HttpContext.Request.Params[fieldName];