OrchardCMS / Orchard

Orchard is a free, open source, community-focused Content Management System built on the ASP.NET MVC platform.
https://orchardproject.net
BSD 3-Clause "New" or "Revised" License
2.37k stars 1.12k forks source link

AntiForgeryTokenOrchard Server cannot modify cookies after HTTP headers have been sent. #4676

Open orchardbot opened 9 years ago

orchardbot commented 9 years ago

x0r created: https://orchard.codeplex.com/workitem/20847

I created a login widget - basically a Html.BeginFormAntiForgeryPost with LogOn URL

If I'm not wrong, the issue happens here public static MvcHtmlString AntiForgeryTokenOrchard(this HtmlHelper htmlHelper) { ... // Error is thrown here return htmlHelper.AntiForgeryToken();

The logs are full of this issue originating in the login widget view:

Orchard.Exceptions.DefaultExceptionPolicy - Default - An unexpected exception was caught http://some-url System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Web.HttpException: Server cannot modify cookies after HTTP headers have been sent. at System.Web.HttpResponse.BeforeCookieCollectionChange() at System.Web.HttpCookieCollection.Set(HttpCookie cookie) at System.Web.Helpers.AntiXsrf.AntiForgeryTokenStore.SaveCookieToken(HttpContextBase httpContext, AntiForgeryToken token) at System.Web.Helpers.AntiXsrf.AntiForgeryWorker.GetFormInputElement(HttpContextBase httpContext) at System.Web.Helpers.AntiForgery.GetHtml() at Orchard.Mvc.Html.HtmlHelperExtensions.AntiForgeryTokenOrchard(HtmlHelper htmlHelper) at Orchard.Mvc.Html.MvcFormAntiForgeryPost.Dispose(Boolean disposing) at System.Web.Mvc.Html.MvcForm.Dispose() at ASP._Page_Modules_MODULENAME_WIDGETNAME_cshtml.Execute() in c:\Sites.......Widget.cshtml:line 144 at System.Web.WebPages.WebPageBase.ExecutePageHierarchy() at System.Web.Mvc.WebViewPage.ExecutePageHierarchy() at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) at System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) at System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) at System.Web.Mvc.HtmlHelper.RenderPartialInternal(String partialViewName, ViewDataDictionary viewData, Object model, TextWriter writer, ViewEngineCollection viewEngineCollection) at System.Web.Mvc.Html.PartialExtensions.Partial(HtmlHelper htmlHelper, String partialViewName, Object model, ViewDataDictionary viewData) at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit) at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.<>cDisplayClass26.<>cDisplayClass28.b15(DisplayContext displayContext) at Orchard.DisplayManagement.Descriptors.ShapeAlterationBuilder.<>cDisplayClass3.<>cDisplayClass5.b2(DisplayContext displayContext) at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Process(ShapeBinding shapeBinding, IShape shape, DisplayContext context) at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Execute(DisplayContext context) at Orchard.DisplayManagement.Implementation.DisplayHelper.ShapeExecute(Object shape) at Orchard.DisplayManagement.Implementation.DisplayHelper.Invoke(String name, INamedEnumerable`1 parameters) at Orchard.DisplayManagement.Implementation.DisplayHelper.TryInvoke(InvokeBinder binder, Object[] args, Object& result) at CallSite.Target(Closure , CallSite , Object , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1) at CallSite.Target(Closure , CallSite , Object , Object ) at Orchard.Core.Shapes.CoreShapes.ContentZone(Object Display, Object Shape, TextWriter Output)

orchardbot commented 9 years ago

@sebastienros commented:

Please share your widget code.

orchardbot commented 9 years ago

x0r commented:

Thank you for asking Sebastien.

Cleared the code up for brevity. In my humble opinion, the issue is happening due to crawlers (that cannot handle cookieContainer properly?) Basically the same code is on the login page, but no issues are logged there - that's why I suspect it to be caused by simpler crawlers.

Anyway, you can see it in action here: musqle.com, or maybe you'll prefer musqle.fr ;) and here: logon page

@using (Html.BeginFormAntiForgeryPost(Url.OpenAuthLogOn(returnUrl)))
{
    <fieldset class="group oauth-login">
                @* Oauth button(s) *@
    </fieldset>
}

@using (Html.BeginFormAntiForgeryPost(Url.LogOn(returnUrl)))
{
    <fieldset class=" login-form group">
        @* Login fields + buttons *@
    </fieldset>
}
orchardbot commented 9 years ago

@Piedone commented:

If you have narrowed down the cause please share it. Is it really crawlers?

orchardbot commented 9 years ago

x0r commented:

It's only my best judgement. It's happening only for anonymous:

at ASP._Page_Modules_Softival_Musqle_Extensions_Views_Parts_MusqleProfileWidget_cshtml.Execute() in c:........\Modules\Softival.Musqle.Extensions\Views\Parts\MusqleProfileWidget.cshtml:line 145

orchardbot commented 9 years ago

@DanielStolt commented:

We are having this exact same issue on our production site.

We get this error in the log about 10-20 times every hour.

We are both rendering an authentication form inside a widget it seems. I'm not sure I see how this is related to crawlers though. The exception is thrown when the form is first requested and rendered, which should in no way be affected by how the requesting client does or does not do cookie management?

Let's put our heads together and see what we can figure out. I analyzed our stace traces side-by-side - see the attached screen shot. I'm curious about lines 3 and 5 on the right-hand side. These are not in our stack trace - any idea why?

Here's our stack trace in full:

Orchard.Exceptions.DefaultExceptionPolicy ERROR An unexpected exception was caught
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Web.HttpException: Server cannot modify cookies after HTTP headers have been sent.
   at System.Web.HttpCookieCollection.Set(HttpCookie cookie)
   at System.Web.Helpers.AntiXsrf.AntiForgeryWorker.GetFormInputElement(HttpContextBase httpContext)
   at System.Web.Helpers.AntiForgery.GetHtml()
   at Orchard.Mvc.Html.HtmlHelperExtensions.AntiForgeryTokenOrchard(HtmlHelper htmlHelper)
   at Orchard.Mvc.Html.MvcFormAntiForgeryPost.Dispose(Boolean disposing)
   at System.Web.Mvc.Html.MvcForm.Dispose()
   at ASP._Page_Modules_MiP_Authentication_Views_AuthenticationMethod_PmKod_cshtml.Execute() in e:\Web Sites\CMS\Modules\MiP.Authentication\Views\AuthenticationMethod.PmKod.cshtml:line 26
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
   at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
   at System.Web.Mvc.Html.PartialExtensions.Partial(HtmlHelper htmlHelper, String partialViewName, Object model, ViewDataDictionary viewData)
   at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
   at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.<>c__DisplayClass26.<>c__DisplayClass28.<Discover>b__15(DisplayContext displayContext)
   at Orchard.DisplayManagement.Descriptors.ShapeAlterationBuilder.<>c__DisplayClass3.<>c__DisplayClass5.<BoundAs>b__2(DisplayContext displayContext)
   at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Process(ShapeBinding shapeBinding, IShape shape, DisplayContext context)
   at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Execute(DisplayContext context)
   at Orchard.DisplayManagement.Implementation.DisplayHelper.TryInvoke(InvokeBinder binder, Object[] args, Object& result)
   at CallSite.Target(Closure , CallSite , Object , Object )
   at ASP._Page_Themes_MiP_Theme_Views_LoginMethods_cshtml.Execute() in e:\Web Sites\CMS\Themes\MiP.Theme\Views\LoginMethods.cshtml:line 28
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
   at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
   at System.Web.Mvc.Html.PartialExtensions.Partial(HtmlHelper htmlHelper, String partialViewName, Object model, ViewDataDictionary viewData)
   at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
   at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.<>c__DisplayClass26.<>c__DisplayClass28.<Discover>b__15(DisplayContext displayContext)
   at Orchard.DisplayManagement.Descriptors.ShapeAlterationBuilder.<>c__DisplayClass3.<>c__DisplayClass5.<BoundAs>b__2(DisplayContext displayContext)
   at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Process(ShapeBinding shapeBinding, IShape shape, DisplayContext context)
   at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Execute(DisplayContext context)
   at Orchard.DisplayManagement.Implementation.DisplayHelper.TryInvokeMember(InvokeMemberBinder binder, Object[] args, Object& result)
   at CallSite.Target(Closure , CallSite , Object , Object , Boolean , Boolean )
   at ASP._Page_Themes_MiP_Theme_Views_Parts_Login_Content_cshtml.Execute() in e:\Web Sites\CMS\Themes\MiP.Theme\Views\Parts.Login-Content.cshtml:line 9
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
   at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
   at System.Web.Mvc.Html.PartialExtensions.Partial(HtmlHelper htmlHelper, String partialViewName, Object model, ViewDataDictionary viewData)
   at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit)
   at Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy.ShapeTemplateBindingStrategy.<>c__DisplayClass26.<>c__DisplayClass28.<Discover>b__15(DisplayContext displayContext)
   at Orchard.DisplayManagement.Descriptors.ShapeAlterationBuilder.<>c__DisplayClass3.<>c__DisplayClass5.<BoundAs>b__2(DisplayContext displayContext)
   at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Process(ShapeBinding shapeBinding, IShape shape, DisplayContext context)
   at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Execute(DisplayContext context)
   at Orchard.DisplayManagement.Implementation.DisplayHelper.TryInvoke(InvokeBinder binder, Object[] args, Object& result)
   at CallSite.Target(Closure , CallSite , Object , Object )
   at Orchard.Core.Shapes.CoreShapes.ContentZone(Object Display, Object Shape, TextWriter Output)
orchardbot commented 9 years ago

@DanielStolt commented:

CodePlex piece of shit, nothing works anymore.
Get the side-by-side comparison screenshot here instead then:
https://www.dropbox.com/s/py15huxswgf3cxx/2015-03-21_13-45-48.png?dl=0

orchardbot commented 9 years ago

@jtkech commented:

In MvcFormAntiForgeryPost.cs, in the Dispose() method the AntiForgeryTokenOrchard() extension mentioned by @x0r is used in this line

    _htmlHelper.ViewContext.Writer.Write(_htmlHelper.AntiForgeryTokenOrchard());

Indeed, if I add before the following 2 lines, I get the exact same stack trace as @x0r


    _htmlHelper.ViewContext.HttpContext.Request.Cookies.Clear(); // for testing
    _htmlHelper.ViewContext.HttpContext.Response.Flush(); // for testing
    _htmlHelper.ViewContext.Writer.Write(_htmlHelper.AntiForgeryTokenOrchard());

This because the RequestVerificationToken needs to be generated (because of the 1st line) and headers have been sent (because of the 2nd line). We can have the 1st condition on a first client request and maybe with some crawlers. The 2nd condition can be caused by a response flush or a response redirect that I've also tried

A solution is to check if the headers are already sent. With .net 4.5.2 we can use directly the HttpResponseBase.HeadersWritten property, but here we need to use reflection. Here, a quick hack not embeded in a method, without try / catch blocks...

    var headersWritten = false;
    var headersWrittenProperty = typeof(HttpResponseBase).GetProperty("HeadersWritten");
    if (headersWrittenProperty != null) {
        headersWritten = (bool)headersWrittenProperty
            .GetValue(_htmlHelper.ViewContext.HttpContext.Response);
    }
    if (!headersWritten) {
        _htmlHelper.ViewContext.Writer.Write(_htmlHelper.AntiForgeryTokenOrchard());
    }

Best