I have a working implementation of OData WCF service which now need to be published in IIS with basic custom authentication.
Implementation is based on Microsoft OData example and works perfectly fine under the IIS Express. When I publish it to IIS 7.5 with only Basic Authentication enabled, AuthenticateRequest handler is only called on initial request, which returns status code 401 and asks to authenticate.
AuthenticateRequest is no longer called on subsequent requests. When debugging the service on IIS, BeginRequest is definitely called, it's just AuthenticateRequest not being present in the pipeline? Both are called every request in IIS Express.
IIS Authentication configuration:
IHttpModule code:
public class BasicAuthModule: IHttpModule
{
// based on http://msdn.microsoft.com/en-gb/data/gg192997.aspx
public void Init(HttpApplication app)
{
app.AuthenticateRequest += AuthenticateRequest;
app.BeginRequest += BeginRequest;
}
private void BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
if(app.Context == null)
{
throw new Exception("Will not happen");
}
}
private void AuthenticateRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
if(!app.Request.Headers.AllKeys.Contains("Authorization"))
{
CreateNotAuthorizedResponse(app, 401, 1, "Please provide Authorization headers with your request.");
app.CompleteRequest();
}
else if(!BasicAuthProvider.Authenticate(app.Context))
{
CreateNotAuthorizedResponse(app, 401, 1, "Logon failed.");
app.CompleteRequest();
}
}
private static void CreateNotAuthorizedResponse(HttpApplication app, int code, int subCode, string description)
{
var response = app.Context.Response;
// response.Status = "401 Unauthorized";
response.StatusCode = code;
response.SubStatusCode = subCode;
response.StatusDescription = description;
// response.AppendHeader("WWW-Authenticate", "Basic");
// response.End();
}
public void Dispose()
{
}
}
Web.config:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="BasicAuthModule" type="WcfTestService.BasicAuthModule"/>
</modules>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
And the question: why authentication works in Visual Studio 2012 debug server but not in IIS 7.5? Complete test project can be downloaded from here.
Edit:
I commented out excessive testing code in the CreateNotAuthorizedResponse function and response.End() which caused an exception (I added it last minute before posting).
When inspecting requests it looks like everything should work except that Cookie may be causing IIS to skip authentication for some reason. Below first 2 raw request - reply pairs:
Request 1:
GET http://localhost:8080/test/ HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,pl;q=0.6
Cookie: ASPSESSIONIDAQRDDBTR=BPCFKGDDCGJLPFKHEPOLPMFK; __RequestVerificationToken_L2RlbGl2ZXJ50=j0o-RDC12Z_E1o1nnXU_9iFaThUEPXRXDNKepqoX2fmgjg8gRB6Hi9fs3MSGxUvYQs6tJ0Jxsf6U20WKWpOrj4azgL_VpVzQHcNyJghUrKg1; __RequestVerificationToken=uOeCVgZDguOs3mRA7O4nhj88wJ_mFR6t1QN7vl7mOPGaNBoEnVFmIQVoUwxim8NbODJKMz5fBuAoPKo7Ek-4JeujsOIyIxjRB1xS_JaFF381; .ASPXAUTH=C2965A60E4BB162123A2CDDA8FD825C9DF3625116E5722C9B873BA64F041CCDCAB098EA3A208C2061D8D5746BC0832413105BA274C1B37DB8276471D49DE12562E4E93933289828427F559057519E75421493909E215EAA0DFB4C8DBE213EAC19AB6025EA715658A8D57CAFA308F7AC4A9051687777D2E82B7A2552917466E7C0BFA0C23EEE272F7E83C3718371375358B1199F155FB882EF8F5082CB28F6E030146DE365B5E4D8FE25E55EDD3F03788
Reply 1: (created by CreateNotAuthorizedResponse method)
HTTP/1.1 401 Please provide Authorization headers with your request.
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
WWW-Authenticate: Basic realm="localhost"
X-Powered-By: ASP.NET
Date: Mon, 20 Oct 2014 13:03:14 GMT
Content-Length: 6607
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IIS 7.5 Detailed Error - 401.1 - Please provide Authorization headers with your request.</title>
Request 2 (when entered test:test - dGVzdDp0ZXN0):
GET http://localhost:8080/test/ HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Authorization: Basic dGVzdDp0ZXN0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,pl;q=0.6
Cookie: ASPSESSIONIDAQRDDBTR=BPCFKGDDCGJLPFKHEPOLPMFK; __RequestVerificationToken_L2RlbGl2ZXJ50=j0o-RDC12Z_E1o1nnXU_9iFaThUEPXRXDNKepqoX2fmgjg8gRB6Hi9fs3MSGxUvYQs6tJ0Jxsf6U20WKWpOrj4azgL_VpVzQHcNyJghUrKg1; __RequestVerificationToken=uOeCVgZDguOs3mRA7O4nhj88wJ_mFR6t1QN7vl7mOPGaNBoEnVFmIQVoUwxim8NbODJKMz5fBuAoPKo7Ek-4JeujsOIyIxjRB1xS_JaFF381; .ASPXAUTH=C2965A60E4BB162123A2CDDA8FD825C9DF3625116E5722C9B873BA64F041CCDCAB098EA3A208C2061D8D5746BC0832413105BA274C1B37DB8276471D49DE12562E4E93933289828427F559057519E75421493909E215EAA0DFB4C8DBE213EAC19AB6025EA715658A8D57CAFA308F7AC4A9051687777D2E82B7A2552917466E7C0BFA0C23EEE272F7E83C3718371375358B1199F155FB882EF8F5082CB28F6E030146DE365B5E4D8FE25E55EDD3F03788
Response 2 (BeginRequest called but not AuthenticateRequest):
HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
WWW-Authenticate: Basic realm="localhost"
X-Powered-By: ASP.NET
Date: Mon, 20 Oct 2014 13:03:17 GMT
Content-Length: 6531
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IIS 7.5 Detailed Error - 401.1 - Unauthorized</title>
I think you are mixing up IIS built-in basic authentication with your own custom authentication module. The short answer is to disable Basic Authentication in IIS and enable Anonymous. This will pass all the auth work onto asp.net.
If your are testing in VS I'm assuming you are doing so via a browser that is auto launched when you hit F5.
With basic auth turned on IIS initially responds with a 401 which causes the browser to display the login form.
The credentials you enter there have to be valid windows credentials, which IIS validates against your windows accounts. Once IIS has validated these credentials it will pass the request along to your code.
If you enter valid windows credentials the event is raised but your code will reject it because the credentials are not test/test and return a 401.1
If you enter test/test then IIS is rejecting the credentials and sends back a 401 so your event is never called.
Final word: You should be testing your web service using an http client (e.g. unit test with System.Net.WebClient), or use a chrome plugin (postman/devhttp) to test at the http level. If you are already doing this then forgive my assumption.