HttpListener, Authentication and ASP.NET

One cool thing about HttpListener is that it gives you Basic, Digest and Negotiate authentication for free (and SSL too – btw). You don’t have to write a single line of code to get these authentication schemes in your own HTTP listening application, e.g. an embedded web server (maybe with ASP.NET support), WCF ServiceHost or WSE with HttpListener transport.

These four lines of code set up a HTTP listener on /HttpListener with negotiate (NTLM/Kerberos) authentication:

HttpListener listener = new HttpListener();

listener.Prefixes.Add(http://localhost/HttpListener/”);

listener.AuthenticationSchemes = AuthenticationSchemes.Negotiate; 

listener.Start();

When you have a connection (after calling GetContext()) – you will see a User property on the HttpListenerContext class of type IPrincipal. In the case of Negotiate and Digest you will get a WindowsPrincipal – Basic Authentication results in an HttpListenerBasicIdentity which contains the username and password in clear text – authentication is up to you then (e.g. by using the same technique that IIS uses – call Win32 LogonUser to verify credentials and create a token).

while (true)

{

  HttpListenerContext ctx = listener.GetContext();

  Console.WriteLine(“Request for: “ + ctx.Request.Url.LocalPath);

  Console.WriteLine(“Client: “+ ctx.User.Identity.Name);

  ProcessContext(ctx);

}

Let’s say you also want to integrate ASP.NET into this web server – how do you transport the authenticated user into the ASP.NET world?

Well – the glue between ASP.NET and its host is an HttpWorkerRequest derived class. When you host ASP.NET in IIS the System.Web.Hosting.ISAPIWorkerRequest class and its derivations are used to provide all request specific info from IIS to ASP.NET.

In most samples for custom ASP.NET hosting you’ll find, the SimpleWorkerRequest class is used which is kind of a minimal implementation to support GET-only requests. But you can inherit from this class to add more features when you need them.

When the security context in ASP.NET is constructed, the runtime calls into the GetUserToken() method on your worker request and expects to receive a Windows token for the authenticated user, this is used to populate Request.LogonUserIdentity.

To support the WindowsAuthenticateModule which in turn populates Context.User (which gets synced to Thread.CurrentPrincipal) and makes role based security possible, you have to implement a little bit more plumbing.

The WindowsAuthenticationModule first looks for two server variables, LOGON_USER and AUTH_TYPE – if they are correctly populated, it again calls GetUserToken and combines these three pieces of information to construct a WindowsPrincipal.

To make this all work, derive from SimpleWorkerRequest (or directly from HttpWorkerRequest) and add a possibility (e.g. via a ctor) to pass over the WindowsIdentity from HttpListenerContext. Implement GetUserToken() to return the token found inside the WindowsIdentity whereas GetServerVariable() returns the Name and the AuthenticationType (also found on the identity) back to ASP.NET.

class AuthenticatedSimpleWorkerRequest : SimpleWorkerRequest

{

  WindowsIdentity _clientIdentity = null;

 

  public AuthenticatedSimpleWorkerRequest(

    string page, string queryString, TextWriter output,

    WindowsIdentity clientIdentity)

    : base(page, queryString, output)

  {

    _clientIdentity = clientIdentity;

  }

 

  public override IntPtr GetUserToken()

  {

    return _clientIdentity.Token;

  }

 

  public override string GetServerVariable(string name)

  {

    switch (name)

    {

      case “LOGON_USER”:

        return _clientIdentity.Name;

      case “AUTH_TYPE”:

        return _clientIdentity.AuthenticationType;

    }

 

    return base.GetServerVariable(name);

  }

}

Afterwards just use this new WorkerRequest to call HttpRuntime.ProcessRequest().

class AspNet : MarshalByRefObject

{

  public void ProcessRequest(

    string page, TextWriter w, WindowsIdentity clientIdentity)

  {

    AuthenticatedSimpleWorkerRequest wr =

      new AuthenticatedSimpleWorkerRequest(page, null, w, clientIdentity);

       

    HttpRuntime.ProcessRequest(wr);

  }

}

 

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s