I have a weird problem with a simple authentication filter not firing when the website is run on my local IIS. If I publish to Azure the authentication filter works fine. So I'm suspecting a setting on my local IIS but other than enabling "Basic Authentication" under the website's Authentication settings, I really don't know what other setting it could be.
I've created an empty Web API 2 website which creates the values controller. I've then added the following boilerplate code for an authentication filter:-
public class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public bool AllowMultiple { get { return false; } }
public Task AuthenticateAsync(HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
var req = context.Request;
// Get credential from the Authorization header
//(if present) and authenticate
if (req.Headers.Authorization != null &&
"Basic".Equals(req.Headers.Authorization.Scheme,
StringComparison.OrdinalIgnoreCase))
{
if (TryValidateCredentials(req.Headers.Authorization.Parameter))
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, "AuthenticatedUser"),
new Claim(ClaimTypes.Role, "user")
};
var id = new ClaimsIdentity(claims, "Token");
var principal = new ClaimsPrincipal(new[] { id });
// The request message contains valid credential
context.Principal = principal;
}
else
{
// The request message contains invalid credential
context.ErrorResult = new UnauthorizedResult(
new AuthenticationHeaderValue[0], context.Request);
}
}
return Task.FromResult(0);
}
private bool TryValidateCredentials(string creds)
{
string pair;
try
{
pair = Encoding.UTF8.GetString(Convert.FromBase64String(creds));
}
catch (FormatException)
{
return false;
}
catch (ArgumentException)
{
return false;
}
var ix = pair.IndexOf(':');
if (ix == -1) return false;
var username = pair.Substring(0, ix);
var pw = pair.Substring(ix + 1);
if (username != "" && pw != "")
{
return true;
}
else
{
return false;
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
context.Result = new ResultWithChallenge(context.Result);
return Task.FromResult(0);
}
public class ResultWithChallenge : IHttpActionResult
{
private readonly IHttpActionResult next;
public ResultWithChallenge(IHttpActionResult next)
{
this.next = next;
}
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = await next.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "Please supply valid credentials"));
}
return response;
}
}
}
I've then prefixed the ValuesController class with the following attributes:-
[TestBA.Filters.BasicAuthentication]
[Authorize]
My default IIS7 website (windows 7 Home Premium) points to the project folder and if I run and debug w3wp.exe (Managed v4) then hitting the website at http://192.168.1.11/api/values results in a breakpoint at the start of AuthenticateAsync being hit. If I call from Android code I also hit the same breakpoint. However if I set the basic authentication header either using AsyncHttpClient.setBasicAuth or else add the header manually, then the breakpoint never gets hit and I get an immediate Unauthorized HTTP response. If I publish the website to Azure then it works fine so the unauthorized response without calling AuthenticateAsync only happens on my local IIS. Here's the Android code if it helps:-
private void setBasicAuth(AsyncHttpClient client){
client.setBasicAuth("test", "test");
/*client.addHeader("Authorization",
"Basic " + android.util.Base64.encodeToString(
"test:test".getBytes(),
android.util.Base64.NO_WRAP
)
);*/
}
I've even turned on all possible Exceptions in Visual Studio Community 2013 to see if I was missing a runtime exception or native code exception, but nothing. I'm using the loopj library on Android if that is of any relevance. Also I'm writing the Android code on the native Android IDE "AIDE" if that is of any relevance. The IIS logs show the requests are coming in and being responded to with a 401 whether I make the call with or without the basic authentication header so the requests are definitely working in both cases.
If anyone could shed some light on why a Web API 2 authentication filter would not fire when a basic authentication header is specified in the request on a local copy of IIS then I would be very grateful.
I rewrote the test app on my work PC and couldn't get it to fail so then it was a case of spot the difference.
The answer is actually pretty obvious. I had blindly enabled "Basic Authentication" on my local IIS because naturally I was wanting to use basic authentication in my Web API 2 website. Wrong! Enabling basic authentication in IIS tells IIS to intercept and process all basic authentication headers without passing them on to the website. It actually tries to match usernames and passwords with local PC user accounts. What I needed was to disable basic authentication in IIS, that then allowed the authorization HTTP header to be passed through to my website and caused the AuthenticateAsync to kick in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With