Sunday, March 4, 2012

ASP.NET MVC3 AntiForgeryToken in AJAX calls


The AntiForgeryToken is a nice feature of the MVC framework to prevent CRSF attacks. It's used as a method of the HtmlHelper class which renders a hidden input element containing the key. The problem arises when you want to use it in AJAX call.
A solution for this is to create a wrapper method that extracts the created value and returns it. After that you can store it in the ViewState so it can be reused in the same page for multiple ajax calls.

We define a new class, with 2 constant strings defining the ViewState-keys for the entries defining the Name and the Value of the AntiForgeryToken:

public class SecurityUtility
{
    const string antiForgeryTokenValueKey = "antiForgeryTokenValue";
    const string antiForgeryTokenNameKey = "antiForgeryTokenName";
}


We add the 2 public methods that are going to be called to return the Name and the Value:

/// Returns the AntiForgeryToken Value
public static string GetAntiForgeryTokenValue(HtmlHelper html)
{
    string value = (string)html.ViewData[antiForgeryTokenValueKey];
    if (string.IsNullOrEmpty(value))
    {
        InitAntiForgeryToken(html);
    }

    return (string)html.ViewData[antiForgeryTokenValueKey];
}

/// Returns the AntiForgeryToken Name
public static string GetAntiForgeryTokenName(HtmlHelper html)
{
    string name = (string)html.ViewData[antiForgeryTokenNameKey];
    if (string.IsNullOrEmpty(name))
    {
        InitAntiForgeryToken(html);
    }

    return (string)html.ViewData[antiForgeryTokenNameKey];
}

These tow methods return the values form the ViewState. In case that the values are not defined, the InitAntiForgeryToken() method is called which will initialize these values:


private static void InitAntiForgeryToken(HtmlHelper html)
{
    string input = html.AntiForgeryToken().ToHtmlString();

    // Name
    Regex r = new Regex("name=\"(?[^\"]*)");
    Match m = r.Match(input);
    if (m.Success && m.Groups["tokenName"] != null &&
        m.Groups["tokenName"].Success)
    {
        html.ViewData[antiForgeryTokenNameKey] =
            m.Groups["tokenName"].Value;
    }

    // Value
    r = new Regex("value=\"(?[^\"]*)");
    m = r.Match(input);
    if (m.Success && m.Groups["tokenValue"] != null &&
        m.Groups["tokenValue"].Success)
    {
        html.ViewData[antiForgeryTokenValueKey] =
            m.Groups["tokenValue"].Value;
    }
}

This method first takes the result of the HtmlHelper.AntiForgeryToken() method as a html string and then uses regular expressions to get the name and the value of the generated hidden input element. These values are then stored in the ViewState.

Finally, in the View when we're generating an ajax call we can add the AntiForgeryToken into the data like this:

.ajax({
    url: "theActionUrl...",
    type: "POST",
    data: {
        "@SecurityUtility.GetAntiForgeryTokenName(Html)" :
            "@SecurityUtility.GetAntiForgeryTokenValue(Html)"
    }
});

-----------------------------

The Whole SecurityUtility class is given in the next code block:

public class SecurityUtility
{
    const string antiForgeryTokenValueKey = "antiForgeryTokenValue";
    const string antiForgeryTokenNameKey = "antiForgeryTokenName";

    public static string GetAntiForgeryTokenValue(HtmlHelper html)
    {
     string value = (string)html.ViewData[antiForgeryTokenValueKey];
     if (string.IsNullOrEmpty(value))
     {
        InitAntiForgeryToken(html);
     }

     return (string)html.ViewData[antiForgeryTokenValueKey];
    }


    public static string GetAntiForgeryTokenName(HtmlHelper html)
    {
     string name = (string)html.ViewData[antiForgeryTokenNameKey];
     if (string.IsNullOrEmpty(name))
     {
        InitAntiForgeryToken(html);
     }

     return (string)html.ViewData[antiForgeryTokenNameKey];
    }

    private static void InitAntiForgeryToken(HtmlHelper html)
    {
     string input = html.AntiForgeryToken().ToHtmlString();

     // Name
     Regex r = new Regex("name=\"(?[^\"]*)");
     Match m = r.Match(input);
     if (m.Success && m.Groups["tokenName"] != null && m.Groups["tokenName"].Success)
     {
        html.ViewData[antiForgeryTokenNameKey] = m.Groups["tokenName"].Value;
     }
     if (m.Success && m.Groups["tokenName"] != null && m.Groups["tokenName"].Success)
     {
        html.ViewData[antiForgeryTokenNameKey] = m.Groups["tokenName"].Value;
     }

     // Value
     r = new Regex("value=\"(?[^\"]*)");
     m = r.Match(input);
     if (m.Success && m.Groups["tokenValue"] != null && m.Groups["tokenValue"].Success)
     {
        html.ViewData[antiForgeryTokenValueKey] = m.Groups["tokenValue"].Value;
     }
    }
}