In my previous article, Purpose Of ValidateAntiForgeryToken In MVC Application, we have seen how we could avoid a scripting attack, coming from a miscellaneous site, by using built-in ValidateAntiForgeryToken in our application.
I believe everyone is now familiar with CSRF or XSS concept - what it is and how we can secure our application with the use of built-in helpers from these types of attacks. So now, let's get straight to the point.
Important Points
- As built-in ValidateAntiForgeryToken doesn’t work with AJAX calls, we have to create our own validation method to validate the requests.
- When we add @Html.AntiForgeryToken() in our View, then on load time, it generates cookies and a __RequestVerificationToken. On behalf of these two values, ValidateAntiForgeryToken which we add within Controller post method, validates our page on post time.
- If one of them, either cookie value or token value, doesn’t match, then it displays an error that you all are familiar with.
- Now, with AJAX requests, ValidateAntiForgeryToken will not validate the page. So, we will create a filter class to validate the pages.
Let’s start.
- Create an MVC application and name it whatever you want.
- Choose a basic template.
- Create a Model class and name it as Employee.cs.
- public class Employee {
- public int ID {
- get;
- set;
- }
- public string Name {
- get;
- set;
- }
- public float Salary {
- get;
- set;
- }
- }
- Create a Controller as EmployeeController.cs and a method inside this Controller, as mentioned below.
- public ActionResult Insert() {
- return View();
- }
- Now, right click on Insert and add a View “Insert.cshtml”. Replace the content of View with the following code.
- @model XSS_with_Ajax_Requests.Models.Employee
- @ {
- ViewBag.Title = "Create";
- Layout = "~/Views/Shared/_Layout.cshtml";
- } < h2 > Insert < /h2>
- @using(Html.BeginForm()) {
- @Html.AntiForgeryToken()
- @Html.ValidationSummary() < fieldset > < legend > Enter Details < /legend> < p id = "error"
- style = "color:red" > < /p> < ol > < li > @Html.LabelFor(m => m.Name)
- @Html.TextBoxFor(m => m.Name, new {
- @id = "Name"
- }) < /li> < br / > < li > @Html.LabelFor(m => m.Salary)
- @Html.TextBoxFor(m => m.Salary, new {
- @id = "Salary", @type = "number"
- }) < /li> < /ol> < input type = "button"
- id = "submit"
- value = "Insert" / > < /fieldset>
- }
- Now, under the App_Start folder, open Filter.Config.cs. Add a method inside this, as shown below.
- [AttributeUsage(AttributeTargets.Class)]
- public class ValidateAntiForgeryTokenOnAllPosts: AuthorizeAttribute
- {
- public override void OnAuthorization(AuthorizationContext filterContext) {
- var request = filterContext.HttpContext.Request;
-
- if (request.HttpMethod == WebRequestMethods.Http.Post) {
-
-
- 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);
- }
- }
- }
- }
- Create a context class with Dbset for the application wherever you feel better. I just added this in my Controller.
- public class UsersContext: DbContext
- {
- public UsersContext(): base("DefaultConnection") {}
- public DbSet < Employee > Employee {
- get;
- set;
- }
- }
- Go back to Insert.cshtml and add an AJAX method to post the data.
- <script type="text/javascript">
- $(document).ready(function() {
-
- $('#submit').click(function() {
- var headers = {};
- var token = $('input[name="__RequestVerificationToken"]').val();
- var name = $('#Name').val();
- var salary = $('#Salary').val();
- headers['__RequestVerificationToken'] = token;
- if ($('#Name').val() != "" && $('#Salary').val() != "") {
- $('#error').html('');
- alert(token);
- $.ajax({
- url: 'Employee/SaveData',
- cache: false,
- async: true,
- data: {
- 'name': name,
- 'salary': salary
- },
- type: "POST",
- headers: headers,
- success: function(data) {
- alert(data);
- },
- error: function() {
- alert("Validation token not matching")
- }
- });
- $('#Name').val('');
- $('#Salary').val('');
- } else {
- $('#error').html('Please fill all fields first');
- }
- });
- });
- </script>
- Add ActionResult in EmployeeController to SaveData.
- public ActionResult SaveData(string name, float salary)
- {
- UsersContext usrContext = new UsersContext();
- Employee emp = new Employee();
- emp.Name = name;
- emp.Salary = salary;
- usrContext.Employee.Add(emp);
- usrContext.SaveChanges();
- return Json("Successfull", JsonRequestBehavior.AllowGet);
- }
- Add [ValidateAntiForgeryTokenOnAllPosts] attribute outside your controller class.
Points need to remember,
- Do not forget to resolve namespaces.
- Do not forget to add jquery.js in View and [ValidateAntiForgeryTokenOnAllPosts] attribute outside your Controller class.
- Change connection-string in web.config and create database as well. You can use database first approach to create database (already discussed in last article).
Testing Approach
- Run the application. The page will display as,
- Apply break-point on ValidateAntiForgeryTokenOnAllPosts class and SaveData method in Controller, so that you can test properly.
- Enter Fields and click on insert button. You will see an alert which display token. This alert is just for clarification. You can also check this in Elements with name="__RequestVerificationToken". This has been generated by @Html.AntiForgeryToken(), which we added in our view.
- After this, control goes to ValidateAntiForgeryTokenOnAllPosts class. Now, press F10 and check the values. You will see that this method will only run for post methods. AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
This Antiforgery.validate method will validate the page with cookie value and verification-token respectively. Press F5, You will see an alert with message “Successful”. This message being returned through ajax OnSuccess. This means that page has valid post request.
- Now, run and try to change the cookie value again, as I have changed while debugging.
- Press enter >> F5. Now, you will note that the control will not go to SaveData method and will show and alert with a message as “Validation Token Not Matching”. This means that this is not a valid request as cookie value doesn’t match. That’s it.
Notes,
- You can also change cookies values through inspect element window and test.
- You can also test by changing token value with a dummy value by un-comment this line as already available in ajax post method. “//$('input[name="__RequestVerificationToken"]').val("Estj-MvAS6-1OL9WkXokadU1");”.
- I have used simple validation and just two fields, because this article is not about validations and anything else. I just want to describe the concept.
- Please check ajax post method deeply, you will see the a __RequestVerificationToken validated by AntiForgery.Validate(cookieValue,request.Headers["__RequestVerificationToken"]);
- Method has been passed through ajax post method in headers.
- Else part of ValidateAntiForgeryTokenOnAllPosts class will execute for by default validation method if we are not using ajax post.
- Please build project, if you going to download source code, as I delete the packages.
So, in this manner, we can secure our application from attacks, when we are using ajax post methods. I hope you understand the concept. Feel free to contact me if you have any doubts, and also give your valuable feedback on the article. Happy Coding.