8

Alright all, I've been bashing my head bloody over this one...

The Set Up:

  • I have a Web Api 2.0 Project with Basic Authentication set up.
  • I have CORS enabled in the web.config
  • I have ELMAH logging errors
  • I have a DelegatingHandler handling the incoming request - (code below)
  • I have a very specific data structure so I'm not using Membership or Identity by Microsoft.
  • I'm flushing every request that comes in that uses the OPTIONS verb

The Problem:

I am getting the following error on EVERY Authenticated Request:

System.Web.HttpException (0x80004005): Server cannot set status after HTTP headers have been sent.
   at System.Web.HttpResponse.set_StatusCode(Int32 value)
   at System.Web.HttpResponseWrapper.set_StatusCode(Int32 value)
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseStatusAndHeadersAsync>d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseAsync>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at System.Web.Http.WebHost.HttpControllerHandler.<ProcessRequestAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar)
   at System.Web.HttpTaskAsyncHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
   at System.Web.HttpApplication.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar)

Investigation done so far:

The error listed above is happening only with the OPTIONS verb.

The error says that the headers were already sent before it was asked to set its Status Code.

Only 1 error record appears - in elmah, the "SendAsync" method is hit twice - once for the OPTIONS pre-flight and once for the GET or POST. That tells me that this is only happening on one request - the OPTIONS request.

In order to get past the pre-flight, I'm using this: (global.asax)

protected void Application_BeginRequest()
{
  if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
    {
        Response.Flush();
    }
}

This passes the responses into my handler, but it ALSO passes the OPTIONS verb into this code, which I think it should:

//Capture incoming request by overriding SendAsync method

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    //validate credentials, set CurrentPrincipal and Current.User if valid
    if (ValidateCredentials(request.Headers.Authorization))
    {
        Thread.CurrentPrincipal = new MyPrincipal(_userName);
        HttpContext.Current.User = new MyPrincipal(_userName);
    }


    // Execute base.SendAsync to execute default actions
    // and once it is completed, capture the response object and add
    // WWW-Authenticate header if the request was marked as (401) unauthorized.

    return base.SendAsync(request, cancellationToken).ContinueWith(task =>
    {
        HttpResponseMessage response = task.Result;
        if (response.StatusCode == HttpStatusCode.Unauthorized && !response.Headers.Contains("WWW-Authenticate"))
        {
            response.Headers.Add("WWW-Authenticate", "Basic");
        }
        return response;
    });

}

Now in the debugger, I'm seeing that the OPTIONS goes through and fires a 405 - method not allowed, but when it comes through to elmah it's a 500 error - Server cannot set status after HTTP headers have been sent.

I'm NOT putting OPTIONS on any of my controllers, so that might explain the 405 - but the request shouldn't get that far.

So - this sounds like a config thing? Confused.

My cors config is:

<!-- BEGIN: Enable CORS -->
<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="accept, authorization, origin, content-type" />
    <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
  </customHeaders>
</httpProtocol>

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <remove name="WebDAV"/>
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
<!-- END: Enable CORS -->

My Thoughts so far

The error is not a "FatalError" and the program still fires because the preflight request doesn't effect anything that I'm aware of - so can the error be ignored? Well, NO! It's an error!

Now - with the research I've done, it seems like this is an IIS thing that is leaking into my app that I may be able to stop through config - but how?

If that's not it then what the hell IS causing this? Where is it coming from? How can I fix it? No idea...

So I'm leaning on the valiant wisdom of my fellow nerds on Stack Overflow...

HELP!!!!!!!

user1628627
  • 347
  • 1
  • 3
  • 8
  • You should not need to flush response in begin request. How is `ValidateCredentials` implemented? Also, have you tried enabling CORS using [EnableCorsAttribute](http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api) while setting the `SupportsCredentials` property to `true`? – boosts Apr 20 '15 at 04:40
  • I had set this up before Web Api 2.0. What I may need to do is try to set up a new, test project, to try and get this to work. Then I'll be able to see why this is giving me so much grief. ValidateCredentials just runs a "login" against my custom membership implementation. This gives me more rabbits to chase. – user1628627 Apr 20 '15 at 15:36
  • I'm encountering currently the same issue. I added the .flush as proposed [here](http://stackoverflow.com/questions/27373165/issues-with-cors-in-asp-net) as I was not able to get it running by the attributes as proposed. Still investigating. – devployment Apr 30 '15 at 11:43
  • @user1628627 Did you ever solve this? i am having the same issue and response.end and response.flush arent doing anything – Terrance Jackson Apr 27 '21 at 17:22

1 Answers1

22

Put a Response.End(); right after the Response.Flush();

So you should have an empty response for the preflight request.

At least this solved the issue for me.

devployment
  • 1,961
  • 1
  • 20
  • 32