System.Net.WebClient.UploadStringAsync / POST / AntiForgeryToken issue

Topics: General, Troubleshooting
Jul 1, 2011 at 10:23 AM

Hi all,

I have a controller 

[HttpPost]
public ActionResult Update(int ID, float X, float Y, float Z)
{
            if (ID == -1)
            {
                return Content("You must provide a ID for the object");
            }

            ObjectRecord obj = _objectService.Get(ID);
            if (obj == null)
                return HttpNotFound();

            obj.X = X;
            obj.Y = Y;
            obj.Z = Z;

            return Content("DONE");
}

which works well for GET requests such as

myClient.DownloadStringCompleted += 
 delegate(object sender, DownloadStringCompletedEventArgs e)
 {
     HtmlPage.Window.Invoke("ShowObjectInformation", 
                            mesh.GroupReference.Id);
 };
   
myClient.DownloadStringAsync(
                    new Uri(url, UriKind.RelativeOrAbsolute));

but I'ld like to use POST requests instead such as

myClient.UploadStringCompleted +=
                    delegate(object sender, UploadStringCompletedEventArgs e)
                    {
                        HtmlPage.Window.Invoke("ShowObjectInformation",
                            mesh.GroupReference.Id);
                        Debug.WriteLine("DONE");
                    };

                //myClient.Credentials = new NetworkCredential("admin", "1234567");

                myClient.UploadStringAsync(
                    new Uri(url, UriKind.RelativeOrAbsolute), "");

When I do so, I get an exception in AntiForgeryAuthorizationFilter:

"A required anti-forgery token was not supplied or was invalid."

 

Of course if I comment out the following lines it works perfectly but that's not safe.

var siteSalt = _siteService.GetSiteSettings().SiteSalt;
            var validator = new ValidateAntiForgeryTokenAttribute {Salt = siteSalt};
            validator.OnAuthorization(filterContext);

            if (filterContext.HttpContext is HackHttpContext)
                filterContext.HttpContext = ((HackHttpContext)filterContext.HttpContext).OriginalHttpContextBase;

Any idea?

Thanks for helping

Jul 1, 2011 at 10:42 AM

It's a security procedure, it's purpose is exactly that, to prevent you from posting data from outside of a web browser.

You should GET the security token and then POST your data. Through another method on your Controller. You should look at the ValidateAntiForgeryTokenAttribute on how it generates the validation token.

Jul 1, 2011 at 11:50 AM

I did this:

myClient.DownloadStringCompleted +=
                    delegate(object sender, DownloadStringCompletedEventArgs e)
                    {
                        myClient.UploadStringCompleted +=
                            delegate(object sender2, UploadStringCompletedEventArgs e2)
                            {
                                HtmlPage.Window.Invoke("ShowObjectInformation",
                                    mesh.GroupReference.Id);
                                Debug.WriteLine("DONE");
                            };

                        //myClient.Credentials = new NetworkCredential("admin", "1234567");

                        myClient.UploadStringAsync(
                            new Uri(url, UriKind.RelativeOrAbsolute), "POST", "", e.Result);
                    };
   
                myClient.DownloadStringAsync(
                    new Uri("http://localhost:30320/OrchardLocal/Token", UriKind.RelativeOrAbsolute));

and here the action on my controller:

public ActionResult Token()
        {
            return Content(_siteService.GetSiteSettings().SiteSalt);
        }

Still doesn't work...

I couldn't find the code for ValidateAntiForgeryTokenAttribute

Jul 1, 2011 at 4:44 PM

I believe, the antiforgery token is there to prevent a script from hijacking a valid logged in users session and posting data without their knowledge.  If you do not believe you need this protection you should be able to set the following flag in the Module.txt of the project that contains the controller.

<!-- p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.5px Helvetica} -->

AntiForgery: disabled

Jul 4, 2011 at 7:38 AM

Yeah it is and that's why I want to use antiforgery and be able to do authorized POST requests.

Feb 7, 2012 at 2:03 PM

Hi,

Like I said in my first post: if I comment out the following lines it works.

var siteSalt = _siteService.GetSiteSettings().SiteSalt;
var validator = new ValidateAntiForgeryTokenAttribute {Salt = siteSalt};
validator.OnAuthorization(filterContext);

if (filterContext.HttpContext is HackHttpContext)
    filterContext.HttpContext = ((HackHttpContext)filterContext.HttpContext).OriginalHttpContextBase;

So I commented them to focus on other parts of our project. Now back to security issues, I put those lines back and found a way to do what I wanted.

More familiar with Orchard and MVC now, I followed sharpoverride's suggestion. Thanks by the way. I used Reflector on System.Web.WebPages and went through several functions to finally found how the token is generated. I was almost able to write my own generation function:

(sorry "Insert Code Snippet" didn't allow me to select a language)

string str;

using (MemoryStream stream = new MemoryStream())           

{               

using (BinaryWriter writer = new BinaryWriter(stream))               

{                   

byte[] data = new byte[0x10];                   

var prng = new RNGCryptoServiceProvider();                   

prng.GetBytes(data);                               

writer.Write(_siteService.GetSiteSettings().SiteSalt);                   

writer.Write(Convert.ToBase64String(data));                   

writer.Write(DateTime.Now.Ticks);                   

writer.Write(_authenticationService.GetAuthenticatedUser().UserName);                   

str = "" /*Encoder(stream.ToArray())*/;      // how to encode?!!!!                           

  }           

}           

return str;

No way to replace AntiForgeryDataSerializer.Encoder by my own function since I couldn't find its implementation and no way to call it since AntiForgeryDataSerializer is a private class. But if someone can encode the same way this function does, my function will do the trick for token generation.

I had to find another solution and I found it in Orchard code:

public static IHtmlString AntiForgeryTokenValueOrchard(this HtmlHelper htmlHelper)

{

//HAACK: (erikpo) Since MVC doesn't expose any of its methods for generating the antiforgery token and setting the cookie, we'll just let it do its thing and parse out what we need           

var field = htmlHelper.AntiForgeryTokenTheia().ToHtmlString();           

var beginIndex = field.IndexOf("value=\"") + 7;           

var endIndex = field.IndexOf("\"", beginIndex);
        return new HtmlString(field.Substring(beginIndex, endIndex - beginIndex));       

}

Seems like they encountered the same problem as I did ;)

Eventually I use this function in my view code to pass the token to a silverlight application through initParams.

<param name="initParams" value="AntiForgeryToken=@HttpUtility.UrlEncode(Html.AntiForgeryTokenValueOrchard().ToString())" />

and for all post requests made by the silverlight application I add "__RequestVerificationToken=" + AntiForgeryToken to the data sent.

 

Coordinator
Feb 7, 2012 at 8:26 PM

http://stackoverflow.com/questions/9029402/orchard-cms-ajax-anti-forgery-token-when-logged-in/9039504#9039504

Feb 8, 2012 at 6:33 AM

Thanks Sebastien,

Didn't look for answers on the Internet since Jul 1. I should have done it! Glad I came up with the same solution :)