unosquare / embedio

A tiny, cross-platform, module based web server for .NET
http://unosquare.github.io/embedio
Other
1.46k stars 176 forks source link

Exception when using BasicAuthenticationModule with Microsoft HTTP listener #524

Open gabriele-ricci-kyklos opened 3 years ago

gabriele-ricci-kyklos commented 3 years ago

Describe the bug An exception occurs when using the BasicAuthenticationModule with a Microsoft HTTP listener.

To Reproduce Steps to reproduce the behavior: Given this configuration

var server = 
    new WebServer(o => o
        .WithMicrosoftHttpListener() //Microsoft HTTP listener
        .WithUrlPrefix("http://localhost:9696/")
    )
    .WithLocalSessionManager()
    .WithModule(new BasicAuthenticationModule("/").WithAccount("user", "password"))
    .WithWebApi("/api", m => m.WithController<TestController>())
    ;

and the controller

public sealed class TestController : WebApiController
{
    [Route(HttpVerbs.Post, "/probe")]
    public async Task<bool> Probe()
    {
        return await Task.FromResult(true).ConfigureAwait(false);
    }
}

Doing a call with the correct basic auth header results in a 500 error

Request:

POST /api/probe HTTP/1.1 Authorization: Basic dXNlcjpwYXNzd29yZA== User-Agent: PostmanRuntime/7.28.0 Accept: / Cache-Control: no-cache Postman-Token: ce61e871-b3de-47ea-84df-a05d4da5fe1b Host: localhost:9696 Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Length: 0

Response:

HTTP/1.1 500 Internal Server Error Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Transfer-Encoding: chunked Content-Type: text/html Content-Encoding: gzip Expires: Sat, 26 Jul 1997 05:00:00 GMT Last-Modified: Fri, 11 Jun 2021 12:36:44 GMT Vary: Accept-Encoding Server: Microsoft-HTTPAPI/2.0 Date: Fri, 11 Jun 2021 12:36:44 GMT [html of the 500 error page here]

The swan logger says

14:40:32.442 ERR >> [WebServer] [FqwO3gdLIEyYjgf7+GCVcg] Unhandled exception. $type : System.ArgumentException Message : The 'WWW-Authenticate' header must be modified using the appropriate property or method. Parameter name: name ParamName : name TargetSite : Void ThrowOnRestrictedHeader(System.String) StackTrace : at System.Net.WebHeaderCollection.ThrowOnRestrictedHeader(String headerName) at System.Net.WebHeaderCollection.Set(String name, String value) at EmbedIO.Authentication.BasicAuthenticationModuleBase.d7.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 EmbedIO.WebModuleBase.d20.MoveNext() --- End of stack trace from previous location where exception was thrown --- at EmbedIO.ExceptionHandler.d16.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 EmbedIO.WebModuleBase.d20.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 EmbedIO.Internal.WebModuleCollection.d3.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.ValidateEnd(Task task) at EmbedIO.WebServerBase`1.d41.MoveNext() Source : System HResult : -2147024809

Expected behavior HTTP status code 200 and correct call response (true in this case)

Important note: everything is working with the EmbedIO HTTP Listener

rdeago commented 3 years ago

Hello @gabriele-ricci-kyklos, thanks for the heads-up.

So, my beloved HttpListener strikes again. 🙈 Looks like the WWW-Authenticate header cannot simply be set on a WebHeaderCollection if it was created for use by HttpListenerResponse: we should use the AddHeader method instead to bypass some restrictions Microsoft chose to put in place to make our lives a bit less boring.

Unfortunately, EmbedIO's IHttpResponse interface has no AddHeader method. Adding it wouldn't be a breaking change, but still feels redundant and too "HttpListener-y" to me.

A nice solution would be a façade for WebHeaderCollection that always calls SetInternal instead of Set; obviously it would have to retrieve the SetInternal method via reflection. Hackish, ugly as hell, but it would do the job.

I won't be able to work on it for a couple weeks, so if someone comes up with a better idea, please comment below.

gabriele-ricci-kyklos commented 3 years ago

Hi @rdeago. thanks for the update. I can still make it work by using the EmbedIO HTTP Listener, so I'm not stuck. Later I will take a look at the library code, perhaps I can help with this issue. Many thanks