3
votes

Using ASP.NET Web API 2, if I want to return HttpResponseMessage with Unauthorized status code, I'll get different response headers - with or without WWW-Authenticate header field - depending on whether this response message has content or not.

WWW-Authenticate header field is a required field for Unauthorized response according to Status Code Definitions.

And the lack of WWW-Authenticate header field in the response causes an error for the next request.

To see the problem you can create a new Web API project and to add a simple test controller:

public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var responseMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized)
        {
            // Content = new StringContent("You are not authorized"),
        };

        return responseMessage;
    }
}

If the response message doesn't have a content we'll get normal 401 responses for our calls: enter image description here

With responses:

HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
WWW-Authenticate: Bearer
X-SourceFiles: =?UTF-8?B?RDpcUHJvamVjdHNcVGVzdEFwaVxUZXN0QXBpXGFwaVxUZXN0?=
X-Powered-By: ASP.NET
Date: Fri, 01 May 2015 05:31:20 GMT
Content-Length: 0

If we add content to the response message (uncomment the content line), each second response will be not 401, but 500

enter image description here

The responses with 401 will have the following headers:

HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 22
Content-Type: text/plain; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcUHJvamVjdHNcVGVzdEFwaVxUZXN0QXBpXGFwaVxUZXN0?=
X-Powered-By: ASP.NET
Date: Fri, 01 May 2015 05:34:38 GMT

You are not authorized

And the responses with 500 will say

Server cannot append header after HTTP headers have been sent.
Description: an unhandled exception occurred during the execution of the current web request.Please review the stack trace for more information about the error and  where it originated in the code..... Exception Details: System.Web.HttpException: Server cannot append header after HTTP headers have been sent.

So it looks like it happens because previous responses are illegal - they don't contain WWW-Authenticate header attribute.

But it doesn't help even if I try to add WWW-Authenticate manually

public class TestController : ApiController
    {
        public HttpResponseMessage Get()
        {
            var responseMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized)
            {
                Content = new StringContent("You are not authorized"),
            };

            responseMessage.Headers.Add("WWW-Authenticate", "Bearer");

            return responseMessage;
        }
    }

Now it's in the response

HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 22
Content-Type: text/plain; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
WWW-Authenticate: Bearer
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcUHJvamVjdHNcVGVzdEFwaVxUZXN0QXBpXGFwaVxUZXN0?=
X-Powered-By: ASP.NET
Date: Fri, 01 May 2015 05:43:51 GMT

You are not authorized

but I still have every second request 500 instead of 401.

Can anyone clarify what's going on here and how to make it work properly?

1

1 Answers

0
votes

The Server cannot append header after HTTP headers have been sent. error message from the response with 500 is caused by a module trying to write http headers into the response stream once your action has executed - since your action has already written both the headers and the body.

The usual culprits are FormsAuthentication and Owin cookie authentication kicking in and trying to replace 401 with 302 (redirect to login page).

Scenario:
1. your action executed - added header 401 Unauthorized to the headers collection plus wrote the header into the response stream and then wrote the body (You are not authorized) to the response stream.
2. FormsAuthenticationModule sees 401 Unauthorized header in the response headers collection and attempts to change that to 302 Redirect to login page, plus attempts to write the corresponding header to the response stream - which fails with Server cannot append header after HTTP headers have been sent., since the headers were already written earlier.

Possible fixes:

1) if FormsAuthenticationModule is in your request pipeline

  • consider disabling it if you are not using it
  • in .net 4.5 you you can suppress forms authentication redirection by setting HttpResponse.SuppressFormsAuthenticationRedirect property to false
  • if not on .net 4.5, consider using HttpModule which will suppress this redirection

Phil Haack blogged on all the above options.

2) if using OWIN cookie authentication - consider not setting LoginPath property