Hello Demis,
thanks for your answer. Especially getting the original request was helpful to understand the differences in hosting from
ASP.NET to a plain HttpListener.
However I do think that NTLM/Windows authentication should be possible using HttpListener. Otherwise setting HttpListener.AuthenticationSchemes to IntegratedWindowsAuthentication or Ntlm would not make sense. So I tried further and have come up with the following ideas.
During initialization of the service (AppHostHttpListenerBase), I have overridden Start to set authentication schemes on the listener directly. It is possible to set two schemes, however when a non-authenticated scheme is mixed with e.g. Ntlm or IntegratedWindowsAuthentication a Selector needs to be implemented to determine which authentication scheme is to be used to process a request otherwise the server will never block any request and require authentication:
this.Listener.AuthenticationSchemeSelectorDelegate = request => {
if( request.RawUrl.Contains("/secure") return AuthenticationSchemes.IntegratedWindowsAuthentication;
else return AuthenticationSchemes.Anonymous;
};
So far this serves well at telling the server to require authentification on the detected URLs via the selector delegate. I have then used Fiddler to find out what is going on under the hoods. I have also found the following information about NTLM authentication via HTTP (it was
referenced in a comment on Ayende Rahien's blog mentioned earlier):
1: C --> S GET ...
2: C <-- S 401 Unauthorized
WWW-Authenticate: NTLM
3: C --> S GET ...
Authorization: NTLM <base64-encoded type-1-message>
4: C <-- S 401 Unauthorized
WWW-Authenticate: NTLM <base64-encoded type-2-message>
5: C --> S GET ...
Authorization: NTLM <base64-encoded type-3-message>
6: C <-- S 200 Ok
So in essence we see three requests from the client coming in. The first actually produces an error and thus only informs the client that authentication is required to proceed, so a second request is sent accordingly with the "challenge". The server returns his "response" (type-2) which is actually another error and allows the client to send the third request with a type-3 message. This type3 message being the actual one containing the identity data of the logged-in user that I want to extract on the server side.
Only with the third message the program flow would actually reach the service implementation itself. However I haven't found a way to extract the identity data there, not even when I retrieved the "OriginalRequest". So what I've come up with looks more like a dirty hack, but it makes logical sense so far. I appreciate your comment on whether this might have any unwanted side-effects or makes no sense at all. What I do is override the ProcessRequest method which allows to access HttpListenerContext and inject the detected identity as a custom HTTP header:
protected override void ProcessRequest( HttpListenerContext context )
{
//make sure the custom header is not present so injecting it from outside does no harm
if( context.Request.Headers[ "MYINTEGRATEDUSER" ] != null ) context.Request.Headers.Remove( "MYINTEGRATEDUSER" );
//check for successful authentication and inject the header
{
}
base.ProcessRequest( context );
}
The header can then be read using base.RequestContext.GetHeader("MYINTEGRATEDUSER") in the service implementation.
Of course I appreciate your comment... Sorry if this all seems trivial to you but I'm new to this and try to make my way into the low-level http world.
Cheers,
Helge