Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an MVC HtmlHelper from a WebForm

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.

like image 752
Peter J Avatar asked Aug 28 '09 15:08

Peter J


2 Answers

I know this is an old question, but I ran into this issue today and thought I'd share. I'm working in MVC4 and have a webform control (.ascx) that's shared between both MVC (via RenderPartial) and WebForms. In that control, I needed an antiforgery token. Fortunately, there's a helper now you can use in your webforms now that's as simple as this:

<%= AntiForgery.GetHtml() %>

This will render your anti-forgery token like you would get in MVC.

Here's the MS link to it.

like image 162
pvanhouten Avatar answered Sep 30 '22 16:09

pvanhouten


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.

like image 27
Keith Avatar answered Sep 30 '22 15:09

Keith