views:

633

answers:

3

I'm in the process of adding some UI functionality to a hybrid WebForms/MVC site. In this case, I'm adding some AJAX UI features to a WebForms page (via jQuery), and the data is coming from an MVC JsonResult. Everything is working 100%, with one exception:

I would like to implement the XSRF protection of AntiForgeryToken. I have used it in combination with the ValidateAntiForgeryToken attribute on my pure MVC applications, but would like to know how to implement the Html.AntiForgeryToken() method in WebForms. Here's an example using a UrlHelper.

I'm having some trouble getting ViewContext / RequestContext "mocked" up correctly. How should I go about using HtmlHelpers within a WebForms page?

Edit: I'm looking to retrieve the AntiForgeryToken from my WebForms page, not from the MVC JsonResult.

A: 

you could create a new HtmlHelper in your controller and then pull the anti xrsf from there:

var htmlHelper = new HtmlHelper(
    new ViewContext(
        ControllerContext, 
        new WebFormView("omg"), 
        new ViewDataDictionary(), 
        new TempDataDictionary()), 
        new ViewPage());

var xsrf = htmlHeler.AntiForgeryToken;

myObject.XsrfToken = xsrf;

return JsonResult(myObject);
free-dom
It is my understanding that the AntiForgeryToken sets a cookie, and injects a hidden form field, so the two can be compared. How does this accomplish that?
Peter J
+1  A: 

By default, ASP.NET WebForms already include measures to validate events and viewstate. Phil Haack talks a little bit about that in the linked post. XSRF mitigation strategies are talked about here (Scott Hanselman) and here (Dino Esposito)

Josh E
Very good links, thanks. I was hoping for a way to use the AntiForgeryToken specifically, because the "consumer" of the MVC web service is on a WebForms page (that I will not be rewriting in MVC.)
Peter J
+3  A: 

The key method is in the MVC source code: GetAntiForgeryTokenAndSetCookie

This creates an instance of an internal sealed class called AntiForgeryData.

The instance is serialised into a cookie "_RequestVerificationToken" + a base 64 encoded version of the application path.

The same instance of AntiForgeryData is serialised into a hidden input.

The unique part of the AntiForgeryData is got with an RNGCryptoServiceProvider.GetBytes()

All of this could be spoofed in a WebForms page, the only messy bit is the serialisation of the hidden sealed class. Unfortunately the key method (GetAntiForgeryTokenAndSetCookie) relies on ViewContext.HttpContext.Request to get at the cookies, while the WebForm needs to use HttpContext.Current.Request instead.


Update

Not much testing and a lot of code, but I think I've cracked this with a little reflection. Where I've used reflection I've left the equivalent line commented out above:

using System;
using System.Reflection;
using System.Web;
using System.Web.Mvc;

/// <summary>Utility to provide MVC anti forgery tokens in WebForms pages</summary>
public class WebFormAntiForgery
{
    /// <summary>Create an anti forgery token in a WebForms page</summary>
    /// <returns>The HTML input and sets the cookie</returns>
    public static string AntiForgeryToken()
    {
        string formValue = GetAntiForgeryTokenAndSetCookie();

        // string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
        var mvcAssembly = typeof(HtmlHelper).Assembly;
        var afdType = mvcAssembly.GetType("System.Web.Mvc.AntiForgeryData");
        string fieldName = Convert.ToString(afdType.InvokeMember(
            "GetAntiForgeryTokenName",
            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod,
            null,
            null,
            new object[] { null }));

        TagBuilder builder = new TagBuilder("input");
        builder.Attributes["type"] = "hidden";
        builder.Attributes["name"] = fieldName;
        builder.Attributes["value"] = formValue;
        return builder.ToString(TagRenderMode.SelfClosing);
    }

    static string GetAntiForgeryTokenAndSetCookie()
    {
        var mvcAssembly = typeof(HtmlHelper).Assembly;
        var afdType = mvcAssembly.GetType("System.Web.Mvc.AntiForgeryData");

        // new AntiForgeryDataSerializer();
        var serializerType = mvcAssembly.GetType("System.Web.Mvc.AntiForgeryDataSerializer");
        var serializerCtor = serializerType.GetConstructor(new Type[0]);
        object serializer = serializerCtor.Invoke(new object[0]); 

        // string cookieName = AntiForgeryData.GetAntiForgeryTokenName(HttpContext.Current.Request.ApplicationPath);
        string cookieName = Convert.ToString(afdType.InvokeMember(
            "GetAntiForgeryTokenName",
            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod,
            null,
            null,
            new object[] { HttpContext.Current.Request.ApplicationPath }));

        // AntiForgeryData cookieToken;
        object cookieToken;
        HttpCookie cookie = HttpContext.Current.Request.Cookies[cookieName];
        if (cookie != null)
        {
            // cookieToken = Serializer.Deserialize(cookie.Value);
            cookieToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null, serializer, new object[] { cookie.Value });
        }
        else
        {
            // cookieToken = AntiForgeryData.NewToken();
            cookieToken = afdType.InvokeMember(
                "NewToken",
                BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod,
                null,
                null,
                new object[0]);

            // string cookieValue = Serializer.Serialize(cookieToken);
            string cookieValue = Convert.ToString(serializerType.InvokeMember("Serialize", BindingFlags.InvokeMethod, null, serializer, new object[] { cookieToken }));

            var newCookie = new HttpCookie(cookieName, cookieValue) { HttpOnly = true };

            HttpContext.Current.Response.Cookies.Set(newCookie);
        }

        // AntiForgeryData formToken = new AntiForgeryData(cookieToken)
        // {
        //     CreationDate = DateTime.Now,
        //     Salt = salt
        // };
        var ctor = afdType.GetConstructor(new Type[] { afdType });
        object formToken = ctor.Invoke(new object[] { cookieToken });

        afdType.InvokeMember("CreationDate", BindingFlags.SetProperty, null, formToken, new object[] { DateTime.Now });
        afdType.InvokeMember("Salt", BindingFlags.SetProperty, null, formToken, new object[] { null });

        // string formValue = Serializer.Serialize(formToken);
        string formValue = Convert.ToString(serializerType.InvokeMember("Serialize", BindingFlags.InvokeMethod, null, serializer, new object[] { formToken }));
        return formValue;
    }
}

Usage is then similar to MVC:

WebFormAntiForgery.AntiForgeryToken()

It creates the same cookie and the same HTML as the MVC methods.

I haven't bothered with the salt and domain methods, but they would be fairly easy to add in.

Keith