Implementing the Anti-Forgery Token with the Telerik MVC Grid

Written by John DeVightJohn DeVight on 29 Dec 2011 19:37

Download Sample Code

Overview

I have developed an ASP.NET MVC application that uses AJAX and the Telerik MVC controls. I needed to ensure that when a user manipulates data, the data is submitted properly from the application. The problem with ASP.NET MVC controllers is that the controller actions are accessed using a URL. If a user wanted bypass the user interface to manipulate data, the user could enter the appropriate url into the browser window and submit the request. A clever hacker could write a simple script to do this as well. I need to to implement additional security to prevent this from happening. I read some articles about implementing the ASP.NET MVC AntiForgeryToken helper, however, since I am using the Telerik MVC controls, I do not have control over the AJAX calls that are made and am unable to include the AntiForgeryToken as part of the AJAX Posts. The solution was to intercept the AJAX requests and add the anti-forgery token.

The following article describes how to add support for the Anti-Forgery token to an Telerik MVC Grid.

Using the ASP.NET MVC AntiForgeryToken helper

The ASP.NET MVC AntiForgeryToken helper is easy to use. Simply add the following to your html markup:

@Html.AntiForgeryToken()

This creates a hidden field on the web page with the name: __RequestVerificationToken

Validating the Anti-Forgery Token in the Controller Action

To require an anti-forgery token to be passed to a controller action, the ValidateAntiForgeryToken attribute needs to be set on every Post Controller Action in the Controller. This can be a bit of a pain if a controller has a lot of post controller actions. Additionally, a post controller action could be added without the ValidateAntiForgeryToken attribute, and would therefore be unprotected. The Anti-Forgery Request Recipes For ASP.NET MVC And AJAX article demonstrates the use of a ValidateAntiForgeryTokenWrapperAttribute wrapper class that can be applied as an attribute on the Controller. This would apply the ValidateAntiForgeryToken attribute to all the post controller actions in the controller.

I added the following class described in the Anti-Forgery Request Recipes For ASP.NET MVC And AJAX article to my project:

using System;
using System.Linq;
using System.Web.Mvc;
 
namespace TelerikAntiForgeryMvcApp.Extensions
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
        AllowMultiple = false, Inherited = true)]
    public class ValidateAntiForgeryTokenWrapperAttribute : FilterAttribute, IAuthorizationFilter
    {
        private readonly ValidateAntiForgeryTokenAttribute _validator;
 
        private readonly AcceptVerbsAttribute _verbs;
 
        public ValidateAntiForgeryTokenWrapperAttribute(HttpVerbs verbs)
            : this(verbs, null)
        {
        }
 
        public ValidateAntiForgeryTokenWrapperAttribute(HttpVerbs verbs, string salt)
        {
            this._verbs = new AcceptVerbsAttribute(verbs);
            this._validator = new ValidateAntiForgeryTokenAttribute()
            {
                Salt = salt
            };
        }
 
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            string httpMethodOverride = filterContext.HttpContext.Request.GetHttpMethodOverride();
            if (this._verbs.Verbs.Contains(httpMethodOverride, StringComparer.OrdinalIgnoreCase))
            {
                this._validator.OnAuthorization(filterContext);
            }
        }
    }
}

I then added the ValidateAntiForgeryTokenWrapper attribute to my HomeController like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Telerik.Web.Mvc;
using TelerikAntiForgeryMvcApp.Models;
 
namespace TelerikAntiForgeryMvcApp.Controllers
{
    [TelerikAntiForgeryMvcApp.Extensions.ValidateAntiForgeryTokenWrapper(HttpVerbs.Post)]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult About()
        {
            return View();
        }
    }
}

Define the Controller Actions for the Telerik MVC Grid

The sample application displays a grid containing a list of all the telerik products (as of 2011). The following methods were added to the HomeController to populate, insert, update and delete in the grid using Ajax requests:

  1. GridProducts - returns a list of products to the grid.
  2. GridInsertProduct - inserts a new product (into the list of products stored in session) from the grid.
  3. GridUpdateProduct - updates a product (in the list of products stored in session) that was updated in the grid.
  4. GridDeleteProduct - delete a product (from the list of products stored in session) in the grid.
  5. GetProducts - protected method that returns the list of products from session. If the list of products are not in session, the list of products is created and stored in session.
  6. UpdateProducts - updates the list of products in session.
  7. CreateProductList - creates the list of products.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Telerik.Web.Mvc;
using TelerikAntiForgeryMvcApp.Models;
 
namespace TelerikAntiForgeryMvcApp.Controllers
{
    [TelerikAntiForgeryMvcApp.Extensions.ValidateAntiForgeryTokenWrapper(HttpVerbs.Post)]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult About()
        {
            return View();
        }
 
        [GridAction]
        public ActionResult GridProducts()
        {
            return View(new GridModel(GetProducts()));
        }
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult GridInsertProduct()
        {
            IList<Product> products = GetProducts();
 
            Product product = new Product();
            TryUpdateModel(product);
            products.Add(product);
            UpdateProducts(products);
 
            return View(new GridModel(GetProducts()));
        }
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult GridUpdateProduct(int id)
        {
            IList<Product> products = GetProducts();
 
            Product product = (from Product p in products
                               where p.Id == id
                               select p).FirstOrDefault();
 
            TryUpdateModel(product);
            UpdateProducts(products);
 
            return View(new GridModel(GetProducts()));
        }
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult GridDeleteProduct(int id)
        {
            IList<Product> products = GetProducts();
 
            Product product = (from Product p in products
                               where p.Id == id
                               select p).FirstOrDefault();
 
            products.Remove(product);
            UpdateProducts(products);
 
            return View(new GridModel(GetProducts()));
        }
 
        protected IList<Product> GetProducts()
        {
            IList<Product> products = Session["Products"] as IList<Product>;
 
            if (products == null)
            {
                products = CreateProductList();
                UpdateProducts(products);
            }
            return products;
        }
 
        protected void UpdateProducts(IList<Product> products)
        {
            Session["Products"] = products;
        }
 
        protected IList<Product> CreateProductList()
        {
            IList<Product> products = new List<Product>();
 
            products.Add(new Product { Id = 1, Name = "ASP.NET AJAX", Category = "Developer Tools", SubCategory = "Web UI Controls & Components", Controls = 44 });
            products.Add(new Product { Id = 2, Name = "ASP.NET MVC", Category = "Developer Tools", SubCategory = "Web UI Controls & Components", Controls = 16 });
            products.Add(new Product { Id = 3, Name = "Silverlight", Category = "Developer Tools", SubCategory = "Web UI Controls & Components", Controls = 59 });
            products.Add(new Product { Id = 4, Name = "WPF", Category = "Developer Tools", SubCategory = "Desktop UI Controls & Components", Controls = 51 });
            products.Add(new Product { Id = 5, Name = "Windows Forms", Category = "Developer Tools", SubCategory = "Desktop UI Controls & Components", Controls = 27 });
            products.Add(new Product { Id = 6, Name = "Windows Phone", Category = "Developer Tools", SubCategory = "Mobile UI Controls & Components", Controls = 24 });
            products.Add(new Product { Id = 7, Name = "Telerik Reporting", Category = "Developer Tools", SubCategory = "Report Designer and Viewer", Controls = 8 });
            products.Add(new Product { Id = 8, Name = "OpenAccess ORM", Category = "Developer Tools", SubCategory = "Data Access", Controls = 8 });
            products.Add(new Product { Id = 9, Name = "Just Code", Category = "Developer Tools", SubCategory = "Productivity Tools", Controls = 12 });
            products.Add(new Product { Id = 10, Name = "Just Mock", Category = "Developer Tools", SubCategory = "Productivity Tools", Controls = 9 });
            products.Add(new Product { Id = 11, Name = "Just Trace", Category = "Developer Tools", SubCategory = "Productivity Tools", Controls = 10 });
            products.Add(new Product { Id = 12, Name = "Just Decompile", Category = "Developer Tools", SubCategory = "Productivity Tools", Controls = 6 });
            products.Add(new Product { Id = 13, Name = "TeamPulse", Category = "Agile Project Management", SubCategory = "", Controls = 9 });
            products.Add(new Product { Id = 14, Name = "Test Studio", Category = "Software Testing Tools", SubCategory = "", Controls = 5 });
            products.Add(new Product { Id = 15, Name = "Testing Framework", Category = "Software Testing Tools", SubCategory = "", Controls = 12 });
            products.Add(new Product { Id = 16, Name = "Sitefinity ASP.NET CMS", Category = "Web Content Management", SubCategory = "", Controls = 18 });
            products.Add(new Product { Id = 17, Name = "Add-ons Marketplace", Category = "Web Content Management", SubCategory = "", Controls = 18 });
            products.Add(new Product { Id = 18, Name = "SharePoint Acceleration Kit", Category = "SharePoint", SubCategory = "" });
            products.Add(new Product { Id = 19, Name = "Telerik Minifier", Category = "Free Tools", SubCategory = "" });
            products.Add(new Product { Id = 20, Name = "Code Converter", Category = "Free Tools", SubCategory = "" });
            products.Add(new Product { Id = 21, Name = "Razor Converter", Category = "Free Tools", SubCategory = "" });
            products.Add(new Product { Id = 22, Name = "Visual Style Builder", Category = "Free Tools", SubCategory = "" });
            products.Add(new Product { Id = 23, Name = "Template Builder", Category = "Free Tools", SubCategory = "" });
 
            return products;
        }
    }
}

Defining the Telerik MVC Grid

The following grid uses AJAX to bind to a list of products by calling the GridProducts controller action defined in the Home controller, and uses AJAX to update a product by calling the GridUpdateProduct controller action defined in the Home controller. The grid also implements the OnLoad client event to call the Index.ProductGrid_onLoad function.

@{
    Html.Telerik().Grid<TelerikAntiForgeryMvcApp.Models.Product>()
        .Name("ProductGrid")
        .DataKeys(keys => keys.Add(p => p.Id))
        .ToolBar(tb =>
        {
            tb.Insert().ButtonType(GridButtonType.Text);
        })
        .Columns(columns =>
        {
            columns.Bound(p => p.Name);
            columns.Bound(p => p.Category);
            columns.Bound(p => p.SubCategory);
            columns.Bound(p => p.Controls);
            columns.Command(commands =>
            {
                commands.Edit().ButtonType(GridButtonType.Text);
            });
        })
        .DataBinding(db =>
            db.Ajax()
                .Select("GridProducts", "Home")
                .Insert("GridInsertProduct", "Home")
                .Update("GridUpdateProduct", "Home")
                .Delete("GridDeleteProduct", "Home")
        )
        .ClientEvents(e => e.OnLoad("Index.ProductGrid_onLoad"))
        .Render();
}

Implementing the jQuery.ajaxSend to intercept AJAX Post Requests

In the Index.ProductGrid_onLoad function, I implement the jQuery.ajaxSend on the ProductGrid element . The handler for the jQuery.ajaxSend event checks to see if the AJAX request is of type "POST", and if it is, gets the Anti-Forgery token and adds it to the AJAX request. Here is the code:

<script type="text/javascript">
 
    var Index = {};
 
    Index.ProductGrid_onLoad = function (e) {
        $('#ProductGrid').ajaxSend(function (e, xhr, settings) {
            if (settings.type == 'POST') {
                var grid = $(e.currentTarget).data('tGrid');
                var tokenData = '__RequestVerificationToken=' + encodeURIComponent(document.getElementsByName('__RequestVerificationToken')[0].value);
                settings.data = tokenData + ((settings.data == null || settings.data.length == 0) ? '' : ('&' + settings.data));
            }
        });
    }
 
</script>

Conclusion

The example demonstrates implementing the Anti-Forgery Token on a Telerik MVC grid for the select, insert, update, and delete actions.

References

Discussion Closed

Add a New Comment

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License