2
votes

I have an ASP.NET WebApi service that requires http basic authentication (this is for a demonstration, not for production, so that's the reason for basic authentication and not something more secure). The service runs fine with visual studio IIS express server and authentication happens through a custom HTTP module.

It fails and continues to popup a login screen when I deploy the site to the hosting server. I verified with Fiddler that the request is being sent and the credentials are being sent. But it keeps responding with a 401 unauthorized response. It appears that the request credentials are somehow being lost in the time it takes to get from the client to the server. I have spent many, many hours trying to diagnose this and the .NET authentication with Web API and IIS seems very confusing. Please help!!

Outgoing request from Fiddler shows:

GET mywebsiteaddress HTTP/1.1
Host: my website address
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0
Accept: /
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Basic YmJvbm5ldDE4Om9jdG9iZXIxNw==
X-Requested-With: XMLHttpRequest
Referer: mysite
Connection: keep-alive

Here are the relevant pieces of my config (I can post more if necessary):

<modules>
  <add name="BasicAuthHttpModule" type="ITMService.Modules.BasicAuthHttpModule"/>
</modules>

<httpModules>
  <add name="BasicAuthHttpModule" type="ITMService.Modules.BasicAuthHttpModule"/>
</httpModules>

<authentication mode="Windows"/>

My custom http module (also working fine in testing from visual studio). This was mostly taken from the example on asp.net :

namespace ITMService.Modules
{
public class BasicAuthHttpModule : IHttpModule
{

    private const string Realm = "www.mysite.net";

    public void Init(HttpApplication context)
    {
        context.AuthenticateRequest += OnApplicationAuthenticateRequest;
        context.EndRequest += OnApplicationEndRequest;

    }

    private static void SetPrincipal(IPrincipal principal)
    {
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
            Log.LogIt("current principal: " + principal.Identity.Name);
        }
    }


    private static bool CheckPassword(string username, string password)
    {
        string passHash = AuthUser.GetUserPassword(username);
        if (PasswordHash.ValidatePassword(password, passHash))
        {
            return true;
        }
        else
        {
            return false;
        }

    }

    private static bool AuthenticateUser(string credentials)
    {
        bool validated = false;

        try
        {
            var encoding = Encoding.GetEncoding("iso-8859-1");
            credentials = encoding.GetString(Convert.FromBase64String(credentials));

            int separator = credentials.IndexOf(':');
            string name = credentials.Substring(0, separator);
            string password = credentials.Substring(separator + 1);

            validated = CheckPassword(name, password);

            if (validated)
            {
                var identity = new GenericIdentity(name);
                SetPrincipal(new GenericPrincipal(identity, null));
            }
        }
        catch (FormatException)
        {
            // Credentials were not formatted correctly.
            validated = false;
            Log.LogIt("not validated");

        }
        return validated;
    }

    private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
    {

        var request = HttpContext.Current.Request;
        var authHeader = request.Headers["Authorization"];
        if (authHeader != null)
        {

            var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

            // RFC 2617 sec 1.2, "scheme" name is case-insensitive
            if (authHeaderVal.Scheme.Equals("basic",
                    StringComparison.OrdinalIgnoreCase) &&
                authHeaderVal.Parameter != null)
            {

                AuthenticateUser(authHeaderVal.Parameter);
            }
        }
    }

    // If the request was unauthorized, add the WWW-Authenticate header 
    // to the response.
    private static void OnApplicationEndRequest(object sender, EventArgs e)
    {

        var response = HttpContext.Current.Response;
        if (response.StatusCode == 401)
        {
            response.Headers.Add("WWW-Authenticate",
                string.Format("Basic realm=\"{0}\"", Realm));
        }
    }

    public void Dispose()
    {
    }
}
}

My IIS server is hosted and running .NET 4 in integrated pipeline mode. I disabled forms authentication and disabled impersonation. I enabled basic authentication and anonymous authentication methods on the server.

I've read countless forum responses and posts about this and nothing has lead me to a clear answer.

3
you may want to use https for basic auth.muratgu
I do plan to use https but I want to get the authentication working to begin with, it's a good point though and I am certainly going to do that.Ben
FYI, you just exposed your credentials, user bbonnet18 pass october17.Diego Queiroz

3 Answers

8
votes

I see two www-Authenticate response headers. I believe your HTTP module is adding one and IIS is adding one. Ensure all kinds of authentication are disabled in IIS, like so. My guess is that you have basic authentication enabled in IIS.

enter image description here

1
votes

Just for completeness:

Most examples of BasicAuthentication are describing to enable "Windows Authentication" in the IIS application configuration. This works in many cases (e.g. a browser connecting to the site) but did not work for a dotNet client using network credentials. Why that?

A brief look via fiddler is giving me following http header:

WWW-Authenticate: Basic realm="My Realm"
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

The browser is choosing BasicAuthentication. But an dotClient is starting the Negotiation (Kerberos) or NTLM authentification.

Case 1: If your server is in the same domain and you are using your normal login credentials => all is fine, the server is doing the negotiation for you

Case 2: If your webapp is providing an own BasicAuthenticationModule for authentication (e.g. has its own user database) the authentication fails.

Solution for case 2: Disable Windows Authentication in the iis or system.webServer-security so that only BasicAuthentication is offered to the client. Then you can use the network credentials to connect to the app without setting the AuthenticationHeader manually or manipulating the authentication cache.

0
votes

There was another question today along the same lines. First step to resolve this problem will be to disable Windows authentication by removing <authentication mode="Windows"/> in Web.config. Also, what is the response message like? What comes back in WWW-Authenticate?