Tuesday, June 13, 2017

Anti forgery tokens on MVC controllers called via JavaScript

In a previous post I demonstrated how an MVC controller (which inherits from SitecoreController) can be used to identify a contact via JavaScript. The next step is to prevent Cross-Site Forgery Requests (CSRF) via the use of an anti forgery token. Because this is a service call and not form post, it's a slightly different method to hook this all together.

Users of AngularJS and Web API can refer to this example: CSRF in AngularJS/Web API on Web Forms.

The anti forgery token

The first step is to place the hidden field on the page which contains the anti-forgery token. This would be placed on the layout, or a view if you are restricting it to certain site features. Web forms users would place this on a master page.
@using System.Web.Helpers
<%@ Import Namespace="System.Web.Helpers" %>
<%# AntiForgery.GetHtml() %>

Token validation

The following attribute will be used on the MVC controller and validates that the token is present and matches the users cookie correctly.
using System.Web.Mvc;

namespace MyProject
    public class ValidateAntiForgeryHeader : System.Web.Mvc.FilterAttribute, System.Web.Mvc.IAuthorizationFilter
        public void OnAuthorization(AuthorizationContext filterContext)
            string clientToken = filterContext.RequestContext.HttpContext.Request.Headers.Get(KEY_NAME);
            if (clientToken == null)
                throw new HttpAntiForgeryException(string.Format("Header does not contain {0}", KEY_NAME));

            string serverToken = filterContext.HttpContext.Request.Cookies.Get(KEY_NAME).Value;
            if (serverToken == null)
                throw new HttpAntiForgeryException(string.Format("Cookies does not contain {0}", KEY_NAME));

            System.Web.Helpers.AntiForgery.Validate(serverToken, clientToken);

        private const string KEY_NAME = "__RequestVerificationToken";

The MVC controller

Now we just need to place the attribute on any actions which require anti forgery. Generally this happens on calls where data is being sent through, such as: login, registration etc. 
public ActionResult ForgottenPassword(string emailAddress)

The service call

Now the service call just needs to pass through the token in the header.
var token = $('input[name="__RequestVerificationToken"]').val();

    type: "POST",
    url: "/Contact/IdentifyContact",
 headers: {
        '__RequestVerificationToken': token
    data: JSON.stringify({'emailAddress': 'testing@email.com'}),
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function (result) { },
    error: function (request, status, error) { }


Now the service calls are protected against CSRF, and of course in a Sitecore implementation this type of service call will still have access to the Sitecore context.

No comments:

Post a Comment