Search code examples
c#signalrowinkatana

Basic Authentication redirects to login page instead of returning an auth challenge


I needed to support, in addition to standard out-of-the-box cookies/forms authentication, also Basic Authentication over HTTPS for SignalR. SignalR runs in the context of a mixed MVC/WebApi site.

After putting the pieces together how to implement this, I used ThinkTecture.IdentityModel.Owin.BasicAuthentication library for this on the server, like this:

app.Map("/basicauth", map =>
{
    map.UseBasicAuthentication("realm", ValidateUser);
    map.MapSignalR<AuthenticatedEchoConnection>("/echo");
    map.MapSignalR();
});

But instead of returning a challenge, I always get a HTTP 302 response that redirects to the Login page of the MVC site. To better debug this, I quickly rolled my own simple OWIN middleware for basic authentication and got the same result. Further testing with a simple mapping like this:

app.Map("/test", map =>
    {
        map.Use((context, next) =>
            {
                // context.Response.StatusCode = 401;
                context.Response.Write("Hello World!");
                return Task.FromResult(0);
            });

revealed that a simple "Hello World" response is returned normally. But when I comment out the the line that sets the response code to 401, I get the redirect to the Login page again. I do not understand this behavior ... why does my MVC site gets involved here and not the 401 response is returned? How can I prevent this?

For OWIN, besides the special /basicauth map above, I had only the top level SignalR mapping in the startup method defined that should continue to work for all cookie authenticated calls:

app.MapSignalR();

Nothing else had been configured by me for OWIN.

Can anybody help me with this?


Solution

  • Ok, I found a way to work around this issue. The first part is to throw out map.MapSignalR<AuthenticatedEchoConnection>("/echo"); from the first sample - it is not necessary and for some reason I not found out it prevented SignalR from working properly.

    The second part, and the workaround for the actual problem, is that in the client I also send the credentials with the first request and do not wait for a challenge. Thus, a 401 never happens and so no redirect to login page. And that's good enough for me.

    So the workaround is, in the client, do not use NetworkCredential like this:

    connection.Credentials = new NetworkCredential(username, password);
    

    Instead, add the Authorization header yourself so it is included already in the first request:

    string creds = string.format("{0}:{1}", username, password);
    string encodedCreds = Convert.ToBase64String(Encoding.Utf8.GetBytes(creds));
    connection.Headers.Add("Authorization", "Basic " + encodedCreds);
    

    In addition, what I found out is that with OWIN, this issue seems to be a more general one and not only related when used with SignalR or even Basic Auth:

    http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/

    Thus, with some modifications to the basic authentication module, it should also be possible to prevent this redirection. I might look into this and will update this post when done.