Introduction
Cross Site Request Forgery (CSRF or XSRF) is a form of attack in which the user authenticates themselves on any website and somehow navigates to another website setup and hosted by any attacker who gets the user to post a form back to the original website containing the information that the attacker specifies.
ASP.NET MVC provides a set of anti-forgery helpers to help preventing such attacks. We use a user-specific token as a hidden field on the form and an attribute applied to the controller action that checks that the correct value was submitted with each POST request.
Here is how I put AntiForgeryToken in the Edit form:
- @using (Html.BeginForm())
- {
- @Html.ValidationSummary(true)
- @Html.AntiForgeryToken()
- <fieldset>
- <legend>Profile</legend>
- @Html.HiddenFor(model => model.ProfileId)
- <div class="editor-label">
- @Html.LabelFor(model => model.Name)
- </div>
- <div class="editor-field">
- @Html.EditorFor(model => model.Name)
- @Html.ValidationMessageFor(model => model.Name)
- </div>
And then an attribute [ValidateAntiForgeryToken] applied to the controller action:
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Edit(Profile profile)
- {
- if (ModelState.IsValid)
- {
- db.Entry(profile).State = EntityState.Modified;
- db.SaveChanges();
- return RedirectToAction("Index");
- }
- return View(profile);
- }
Now run the application and when you view the HTML source code, you'll see the following hidden input:
At the same time, it also issues a cookie with the value encrypted. When the form POST occurs, it compares the issued cookie value and requests a verification token value on the server and ensures that they match; if they don't match then it displays the error "The required anti-forgery form field "_RequestVerificationToken" is not present.".
So, by using @Html.AntiForgeryToken() on the form and the ValidateAntiForgeryToken attribute with the controller action we can avoid CSRF attacks easily and it works great in a typical form post to an action method.
We also POST our forms via jQuery $.post() calls, so how to prevent here? Please read on.
There is a great blog post Preventing CSRF With Ajax by Phil Haack on this title where he discussed prevention by creating some wrapper classes, I recommend that you also read it.
Here I came up with a very simple approach to get it done with just a single line of jQuery code. Before showing the code, I would like to show you something relevant to typical form posting.
As I said earlier, a POST request is only successful when the @Html.AntiForgeryToken() value is on the view page and the ValidateAntiForgeryToken value on the controller method both matches. Here is a screenshot of that showing a POST request sending the RequestVerificationToken to the controller method.
Now, the only thing I need to do when the POST is made by jQuery $.post() calls is to send the RequestVerificationToken to the controller method. Okay, time to see my approach.
- <script type="text/javascript">
- $('#Save').click(function () {
- var token = $('input[name="__RequestVerificationToken"]:nth-child(2)').val();
- var url = "/Profile/CreateProfile";
- var name = $("#Name").val();
- var email = $("#EmailId").val();
- var about = $("#AboutYourself").val();
- $.post(url, { Name: name, EmailId: email, AboutYourself: about, __RequestVerificationToken: token }, function (data) {
- $("#msg").html(data);
- });
- })
- </script>
I underlined the magical line above that will search for the 2nd appearance of RequestVerificationToken on the view page and the $.post() call will send the token to the controller method, here it is:
In the example above you can clearly see I'm able to send the "RequestVerificationToken" back to the controller method for verification and it works fine.
From the jQuery code above you can ask, why use nth-child(2), "two" second appearance. And the answer is, we have two Request Verification Tokens on the form in total, here is a screenshot:
If you use nth-child(3) that is not available instead of nth-child(2), you will get the following error: "The required anti-forgery form field "__RequestVerificationToken" is not present.".
So, you can see this concept works great. I don't know how useful this concept will be? Comments Please.