Avoid Cross Site Scripting (XSS) Attacks While Posting Data Through AJAX In MVC Application

In this article, we will learn how to avoid Cross-Site Scripting attacks when we are posting our data through AJAX POST method in our application.

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

  1. As built-in ValidateAntiForgeryToken doesn’t work with AJAX calls, we have to create our own validation method to validate the requests.
  2. 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.
  3. If one of them, either cookie value or token value, doesn’t match, then it displays an error that you all are familiar with.
  4. Now, with AJAX requests, ValidateAntiForgeryToken will not validate the page. So, we will create a filter class to validate the pages.

Let’s start.

  1. Create an MVC application and name it whatever you want.

    MVC

  2. Choose a basic template.

    basic template
      
  3. Create a Model class and name it as Employee.cs.
    1. public class Employee {  
    2.     public int ID {  
    3.         get;  
    4.         set;  
    5.     }  
    6.     public string Name {  
    7.         get;  
    8.         set;  
    9.     }  
    10.     public float Salary {  
    11.         get;  
    12.         set;  
    13.     }  
    14. }  
  4. Create a Controller as EmployeeController.cs and a method inside this Controller, as mentioned below.
    1. public ActionResult Insert() {  
    2.     return View();  
    3. }  
  5. Now, right click on Insert and add a View “Insert.cshtml”. Replace the content of View with the following code.
    1. @model XSS_with_Ajax_Requests.Models.Employee  
    2. @ {  
    3.     ViewBag.Title = "Create";  
    4.     Layout = "~/Views/Shared/_Layout.cshtml";  
    5. } < h2 > Insert < /h2>  
    6. @using(Html.BeginForm()) {  
    7.     @Html.AntiForgeryToken()  
    8.     @Html.ValidationSummary() < fieldset > < legend > Enter Details < /legend> < p id = "error"  
    9.     style = "color:red" > < /p> < ol > < li > @Html.LabelFor(m => m.Name)  
    10.     @Html.TextBoxFor(m => m.Name, new {  
    11.         @id = "Name"  
    12.     }) < /li> < br / > < li > @Html.LabelFor(m => m.Salary)  
    13.     @Html.TextBoxFor(m => m.Salary, new {  
    14.         @id = "Salary", @type = "number"  
    15.     }) < /li> < /ol> < input type = "button"  
    16.     id = "submit"  
    17.     value = "Insert" / > < /fieldset>  
    18. }  
  6. Now, under the App_Start folder, open Filter.Config.cs. Add a method inside this, as shown below.
    1. [AttributeUsage(AttributeTargets.Class)]  
    2. public class ValidateAntiForgeryTokenOnAllPosts: AuthorizeAttribute  
    3. {  
    4.     public override void OnAuthorization(AuthorizationContext filterContext) {  
    5.         var request = filterContext.HttpContext.Request;  
    6.         // Only validate POSTs  
    7.         if (request.HttpMethod == WebRequestMethods.Http.Post) {  
    8.             // Ajax POSTs and normal form posts have to be treated differently when it comes  
    9.             // to validating the AntiForgeryToken  
    10.             if (request.IsAjaxRequest()) {  
    11.                 var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];  
    12.                 var cookieValue = antiForgeryCookie != null ? antiForgeryCookie.Value : null;  
    13.                 AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);  
    14.             } else {  
    15.                 new ValidateAntiForgeryTokenAttribute().OnAuthorization(filterContext);  
    16.             }  
    17.         }  
    18.     }  
    19. }  
  7. Create a context class with Dbset for the application wherever you feel better. I just added this in my Controller.
    1. public class UsersContext: DbContext  
    2. {  
    3.     public UsersContext(): base("DefaultConnection") {}  
    4.     public DbSet < Employee > Employee {  
    5.         get;  
    6.         set;  
    7.     }  
    8. }  
  8. Go back to Insert.cshtml and add an AJAX method to post the data.
    1. <script type="text/javascript">  
    2.     $(document).ready(function() {  
    3.         //$('input[name="__RequestVerificationToken"]').val("Estj-MvAS6-1OL9WkXokadU1");  
    4.         $('#submit').click(function() {  
    5.             var headers = {};  
    6.             var token = $('input[name="__RequestVerificationToken"]').val();  
    7.             var name = $('#Name').val();  
    8.             var salary = $('#Salary').val();  
    9.             headers['__RequestVerificationToken'] = token;  
    10.             if ($('#Name').val() != "" && $('#Salary').val() != "") {  
    11.                 $('#error').html('');  
    12.                 alert(token);  
    13.                 $.ajax({  
    14.                     url: 'Employee/SaveData',  
    15.                     cache: false,  
    16.                     async: true,  
    17.                     data: {  
    18.                         'name': name,  
    19.                         'salary': salary  
    20.                     },  
    21.                     type: "POST",  
    22.                     headers: headers,  
    23.                     success: function(data) {  
    24.                         alert(data);  
    25.                     },  
    26.                     error: function() {  
    27.                         alert("Validation token not matching")  
    28.                     }  
    29.                 });  
    30.                 $('#Name').val('');  
    31.                 $('#Salary').val('');  
    32.             } else {  
    33.                 $('#error').html('Please fill all fields first');  
    34.             }  
    35.         });  
    36.     });  
    37. </script>  
  9. Add ActionResult in EmployeeController to SaveData.
    1. public ActionResult SaveData(string name, float salary)  
    2. {  
    3.     UsersContext usrContext = new UsersContext();  
    4.     Employee emp = new Employee();  
    5.     emp.Name = name;  
    6.     emp.Salary = salary;  
    7.     usrContext.Employee.Add(emp);  
    8.     usrContext.SaveChanges();  
    9.     return Json("Successfull", JsonRequestBehavior.AllowGet);  
    10. }  
  10. Add [ValidateAntiForgeryTokenOnAllPosts] attribute outside your controller class.

Points need to remember,

  1. Do not forget to resolve namespaces.
  2. Do not forget to add jquery.js in View and [ValidateAntiForgeryTokenOnAllPosts] attribute outside your Controller class.
  3. 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

  1. Run the application. The page will display as,

    Page

  2. Apply break-point on ValidateAntiForgeryTokenOnAllPosts class and SaveData method in Controller, so that you can test properly.

  3. 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.

    Page

  4. 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.

  5. Now, run and try to change the cookie value again, as I have changed while debugging.

    code

  6. 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.