Validating .NET MVC 4 anti forgery tokens in ajax requests
CSRF (Cross-Site Request Forgery) is an attack against a website “whereby unauthorized commands are transmitted from a user that the website trusts.” [Wikipedia]. Protection against this attack is essential for any modern web application.
In the case of .NET MVC, Microsoft have implemented an easy-to-use protection method against CSRF. There’s an attribute in the MVC framework that you can put on your controller actions: ValidateAntiForgeryToken. This works well, but it has a few disadvantages:
-
You must manually decorate all your post actions with the attribute.
It’s easy to forget to do this, so it’s preferable to decorate your entire controller with the attribute (or better yet, a base controller that your whole application uses). Unfortunately, this doesn’t work with the standard ValidateAntiForgeryToken attribute, as this causes all your GET actions to be validated as well (and they will always blow up, as the client doesn’t send any form data with a GET request). -
It doesn’t work with ajax posts.
The standard attribute doesn’t work with ajax because it inspects the Request.Form collection when looking for the token field. When you’re making ajax posts this form is always empty. This is the reason I originally started looking for alternative implementations of the anti forgery token validation.
Fortunately, a solution wasn’t too hard to implement myself. I wrote my own attribute that can a) decorate a controller class and will only validate that class’s POST actions, and b) will work with ajax posts by allowing you to put the anti forgery token in the headers for the request. The attribute class looks like this:
[AttributeUsage(AttributeTargets.Class)] public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute { public override void OnAuthorization( AuthorizationContext filterContext ) { var request = filterContext.HttpContext.Request; // Only validate POSTs if (request.HttpMethod == WebRequestMethods.Http.Post) { // Ajax POSTs and normal form posts have to be treated differently when it comes // to validating the AntiForgeryToken if (request.IsAjaxRequest()) { var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName]; var cookieValue = antiForgeryCookie != null ? antiForgeryCookie.Value : null; AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]); } else { new ValidateAntiForgeryTokenAttribute() .OnAuthorization(filterContext); } } } }
Your client-side Javascript then looks something like this:
var token = $('[name=__RequestVerificationToken]').val(); $.ajax({ type: 'POST', url: '/Home/Ajax', cache: false, headers: { '__RequestVerificationToken': token }, contentType: 'application/json; charset=utf-8', data: { title: 'This is my title', contents: 'These are my contents' }, success: function () { ... }, error: function () { ... } });
The important part of the JS above is the headers: { "__RequestVerificationToken": token }
. This means that you can decorate your base controller with this new ValidateAntiForgeryTokenOnAllPosts attribute, and then all forms in your application will no longer work until you put @Html.AntiForgeryToken() in your form.
2013-02-20 at 02:05
hey thanks for this. help me lot. cheers
2013-03-08 at 16:37
This is great and EXACTLY what I needed! I combined an approach I found here http://stackoverflow.com/questions/14527360/can-i-set-a-global-header-for-all-ajax-requests
to make all of my ajax operations automatically add the needed headers.
$.ajaxSetup({
‘beforeSend’: function (xhr) {
securityToken = $(‘[name=__RequestVerificationToken]’).val();
xhr.setRequestHeader(“__RequestVerificationToken”, securityToken);
}
});
2013-07-10 at 14:08
This is brilliant. I’m on a tight deadline for an initial release, but didn’t want to leave any holes in the app that might be abused. Your code here saved me some hassle. Thanks!
2013-07-11 at 18:27
Out of all that Google recommends, this was and is the cleanest and most appropriate solution. Worked easily and perfectly. Quick, run and post this on Stack…
2013-08-28 at 15:57
How does this work in ajax aplpications where we may not see a page refresh between ajax requests?
Doesn’t it need some way of getting a new anti-forgery token after using one?
2013-09-09 at 07:09
Complex problems have simplest of Solutions.. Cheers 🙂
2013-12-23 at 10:25
Super like, I’ve used your attribute and it works like a charm 🙂
2013-12-31 at 07:53
Hi ,
thanks for the nice post
I found the token generated in html form keep changing every time a new form is rendered. I want to know how these token is generated?
And there is another function, that is “salt” for the antiForgeryToken, but i really know what this used for, even through we don’t use “salt” to generate the token, the token will changes all the time, so why we have such function ?
Thanks in advance
2013-12-31 at 11:30
The token is generated randomly on every page load – and it needs to be. If you used the same token every time then an attacker could very quickly learn what that token is and then it wouldn’t be any use at all.
2014-01-14 at 15:26
During server side token validation on an Ajax post, I get the following error:-
The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
AntiForgery.Validate(cookieValue, HttpContext.Current.Request.Headers[“__RequestVerificationToken”]);
I have a valid machinekey element defined in the web.config, which is being used to encrypt the token. Any help is appreciated, thanks,
2014-02-06 at 22:29
Can’t seem to get this to work at the Method level. Doesn’t run the attribute. Anyway to do this at the Method level and not the Class level?
2014-02-15 at 11:02
If you want to decorate each method individually then you don’t need to use what I wrote – the stuff that MVC has out of the box will do you just fine. Make use of the ValidateAntiForgeryTokenAttribute on each of your action methods.
2014-02-15 at 03:36
Richiban,
I think your solution is better than the one I found at ASP.,net page and the code is definitely better. I had no problem implementing the Java Script and copying the class in the controller, but that is how far I could go. Now, what else needs to be done in the controller for all this to work? What attribute do I need to decorate the ActionResult that is been called by AJAX / Post after the form is submitted ? The way it is now, if I remove the [ValidateAntiForgeryToken] it works, if not it trows an error.. Thanks for any answer. I’m kind of new to MVC / JavaScript
2014-02-15 at 03:43
Richiban,
To better explain my question above, if I decorate with
[ValidateAntiForgeryTokenOnAllPosts] it tells the following:
Attribute ‘ValidateAntiForgeryTokenOnAllPosts’ is not valid on this declaration type. It is only valid on ‘class’ declarations.
Do I need to put your code in a base class and derive the controller from there?
Thanks again
2014-02-15 at 11:09
It sounds like you’re trying to decorate your action method with the Attribute. It should go on your controller – that’s what the error you’re getting says.
2014-02-15 at 15:43
I use an Ajax call to refresh a partial view, but it doesn’t work (as expected) if the “[ValidateAntiForgeryToken]” is decorating the ActionResult in the controller. So, I remove it and it works, Then, I place your class in the same controller, with the attribute “[AttributeUsage(AttributeTargets.Class)]”, exactly like in your post. Everything works so far so good. But then, if I remove the “@Html.AntiForgeryToken();” from the View, it still works without throwing an exception for the invalid (or missing) token. I understood what is the purpose of your code, but I’m basically trying to use it to solve the Ajax call issue with tokens, when the dataType is not html. For what I understood, all I need to do is to place your code in the controller and that is it?
Do I need to decorate the ActionResult with any attribute other than [HttpPost]? What am I missing or not doing correctly?
2014-02-15 at 17:30
Html.AntiForgeryToken has been deprecated and does not for mvc4 rofl
2014-02-16 at 11:43
I don’t think so – the MSDN documentation here shows the attribute in MVC 5 with no signs of its being deprecated: http://msdn.microsoft.com/en-us/library/system.web.mvc.validateantiforgerytokenattribute(v=vs.118).aspx
2014-03-03 at 22:51
Awesome. Really I tired so many methods out there. But none of them explained it so simply like you did. Very good article. Good job.
2014-03-09 at 00:57
Nice! I will keep you idea for use some time later.
In my MVC4 applicaiton, I used the ValidateAntiForgeryToken attribute and then the snippet below (from stack overflow), which append the token to the post data of all my ajax posts, and everything works just fine and is clean/neat too.
var token = $(‘input[name=”__RequestVerificationToken”]’).val();
$.ajaxPrefilter(function (options, originalOptions) {
if (options.type.toUpperCase() == “POST”) {
options.data = $.param($.extend(originalOptions.data, { __RequestVerificationToken: token }));
console.log(options)
}
});
http://stackoverflow.com/questions/19788916/how-to-make-ajax-request-with-anti-forgery-token-in-mvc
2014-03-11 at 16:48
Thanks. the way you did this is very simple and interesting.
2014-04-14 at 13:24
Thank you, worked a treat!
2014-04-24 at 15:08
Hi, I have tried the following but the token is not being added.
I get an error saying token is not present and looking in fiddler I do not see it. I am missing something obvious?
// add AntiForgeryToken to header
var token = $(‘[name=__RequestVerificationToken]’).val();
var headers = {};
headers[“__RequestVerificationToken”] = token;
$.ajax({
type: “POST”,
cache: false,
headers: headers,
url: “/TimeOn/Reject/?employeeId=” + employeeId + “×heetId=” + timesheetId + “&reason=” + $(refTextArea).val(),
dataType: “html”,
success: function (result) {
window.location.reload();
}
});
2014-04-24 at 15:12
First we should check that you do have @Html.AntiForgeryToken() in your form somewhere?
2014-04-24 at 15:53
thanks for the quick reply. my razor view does not have a form. a button is triggering the post.
2014-04-24 at 16:08
again thanks, for now I added a dummy form to the page and put the token there…….got me over this hurdle
2014-04-24 at 16:28
Yes, you should really have a form in the view. A button on its own will still trigger a post, but the “form” is implicit. Without explicitly declaring a form yourself you will not be able to send any information in the post request except the value of the button that was clicked.
2014-04-28 at 09:02
I would appreciate your opinion on the following…..as a global approach for my web app I was thinking of adding a form tag to my layout view and including the antiforgerytoken there. this means all views will have a token and I will not have to worry about including it each time.
Is this a reasonable approach?
Is there any technical or performance reason that this may fail?
Thanks for your help.
/pj
2014-05-01 at 15:06
You could probably make that work, but you’re going to run into problems sooner or later. You will, at some point, need a different form in one of your views and you might get undefined behaviour if you have a form within a form. To me it sounds like you’re not really doing MVC if you’re happy with having a form in the layout. Remember, in MVC you don’t detect “button clicks”, but rather you take the visitor to a URL to perform an action. So, if you have an “order” view and you want a “delete” button on that view, that button is actually just a link to /orders/delete/5911, for example.
2014-05-01 at 14:45
Does this solution work for Ajax.Begin form? Because what I noticed is that if I use Ajax.BeginForm then request.IsAjaxRequest() returns true, but $.ajaxSetup() does not seems to be invoked from broswer, thus the Http header never has the token value, and it fails all the time.
2014-05-01 at 15:10
I’m not too familiar with Ajax.BeginForm, as the Ajax code that I write is almost always by hand. From what you’ve said it looks like it won’t work. You probably want to use something such as
$.post( "test.php", $( "#testform" ).serialize() );
from https://api.jquery.com/jQuery.post/ to do the posting. That way you have control over all aspects of what gets posted, to where, and with what extra data.2014-05-01 at 17:45
I did more digging and found the solution, please see my answer to the question http://stackoverflow.com/questions/21916560/ajax-beginform-with-beforesend/23413300#23413300
2014-11-24 at 19:02
The ValidateAntiForgeryToken DOES work with MS Ajax helper, if form is created with Ajax.BeginForm
2014-07-08 at 11:33
Truly brilliant!
2014-08-27 at 15:03
[…] Based on code from @Richiban here […]
2014-10-22 at 19:43
is this a class? AntiForgeryConfig.CookieName?? or where is this comming from?
2014-10-23 at 12:53
CookieName is a static property on the class System.Web.Helpers.AntiForgeryConfig.
You can see more here: http://msdn.microsoft.com/en-us/library/system.web.helpers.antiforgeryconfig.cookiename(v=vs.111).aspx
2014-12-24 at 01:02
[…] https://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/ […]
2015-07-29 at 05:58
Hi,
I am new in this. Can any one explain how to implement step by step this soluation.? I mean, do i need to create separeted class? What code do i need to add in controller? Sample working example will be fine if you can share.
Thank you.
Regards,
John
2015-08-13 at 18:02
Thanks a ton for this!!!
2015-08-13 at 18:02
Reblogged this on CODEFLIX.
2015-08-17 at 18:41
Hi, I have implemented the entire code shown above. But when comes in the part that the Class “AntiForgery” will validate the two strings (cookieValue and header prop), the values of the two strings are different and the method returns an error: “The required anti-forgery form field “__RequestVerificationToken” is not present.”. Am I doing something wrong?
2016-06-17 at 00:22
Just want to say your article is as astonishing. The clarity on your put up is
just great and i can think you are a professional in this subject.
Well together with your permission allow me to take hold of your feed to stay updated with approaching post.
Thank you a million and please continue the enjoyable work.
2016-08-06 at 21:47
The missing and importantly deciding point on getting this solution for me was the javascript token tag missing the quotes around the name.
The code sample as given didn’t work:
var token = $(‘[name=__RequestVerificationToken]’).val();
This did:
var token = $(‘[name=”__RequestVerificationToken”]’).val();
I nearly gave up in exasperation on this like dozens of other promising solutions out there. This little detail made all the difference. Thanks.
2016-08-08 at 08:55
That’s strange Jonas, it worked for me (and continues to work) without the quotes in Chrome. What browser are you using? I’m glad you got to the bottom of it eventually.
2016-08-09 at 01:22
It was Chrome. It looks to be updated as well. Not sure why the trouble but it’s a simple enough fix once uncovered.