Custom ASP.NET authentication
Lately I worked on integrating ASP.NET web application with external authorization service. Luckily ASP.NET architecture is very extensible and many ASP.NET functionalities are not hard-coded into framework, but provided by instances of IHttpModule. In general those modules allows developers to hook into request processing pipeline and perform custom actions before, during and after request handling. Sole interface is very simple, provides only two methods:
public interface IHttpModule
{
void Dispose();
// Summary:
// Initializes a module and prepares it to handle requests.
// Parameters:
// context:
// An System.Web.HttpApplication that provides access to the methods, properties,
// and events common to all application objects within an ASP.NET application
void Init(HttpApplication context);
}
During application startup ASP.NET runtime will call Init method on all registered modules. Inside Init modules usually attach custom handlers for HttpApplication events, like AuthenticateRequest, AuthorizeRequest, BeginRequest, EndRequest, Error and many others. Standard ASP.NET services like authentication, authorization, output caching and session are provided as modules, so it’s possible (and quite easy!) to switch then to your own implementations.
For example to integrate my external authorization service, I wrote something like this:
public class MyAuthorizationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += new EventHandler(this.HttpApplication_AuthenticateRequest);
}
public void Dispose()
{
}
private void HttpApplication_AuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
string username = this.AuthenticateInExternalSource(application.Context);
if (username != null)
{
GenericIdentity identity = new GenericIdentity(username, "MyAuthentication");
application.Context.User = new GenericPrincipal(identity, new string[0]);
}
}
private string AuthenticateInExternalSource(HttpContext context)
{
// Dummy code
return "Rafał";
}
}
And provided some configuration in web.config:
<configuration>
<!—- You will need to use section <system.webServer> instead when using IIS 7.0 in integrated mode -->
<system.web>
<!-- Disable default Windows authentication -->
<authentication mode="None" />
<httpModules>
<add name="MyAuthenticationModule" type="MyWebApp.MyAuthorizationModule"/>
</httpModules>
</system.web>
</configuration>
That’s all what you need to provide custom authentication service in ASP.NET! Just set User property to your own instance of IPrincipal during handling AuthenticateRequest event, register your module and disable enabled by default Windows authentication. The great part about it is that it perfectly works with other parts of ASP.NET framework, for example with roles provider.
The problem
From my external authentication service I get some more informations than just user name and I wanted to store it inside this User property and use it later. So I quickly created custom IIdentity class and committed changes:
public class MyIdentity : GenericIdentity
{
public MyIdentity(string name, string anotherId)
: base(name, "MyAuthentication")
{
this.AnotherId = anotherId;
}
public string AnotherId { get; private set; }
}
After a while my colleague downloaded my changes, started application and got SerializationException. I quickly found that IIdentity implementations should be serializable, quickly added [Serializable] attribute and again committed changes. This time there was another SerializationException, but with message “Type is not resolved for member…”. After looking for a while for cause, I found Rockford Lhotka post that help me understand what was going on.
Basically setting this User property sets also principal on thread that’s serving given request. The problem lays in that in case of build-in web server in Visual Studio the same thread is used for handling request (in one AppDomain) and for some other tasks (in another one). When you have some objects in one AppDomain and want to use them in another (and that’s the case of our custom principal attached to thread) they can’t be used directly, and must be coped (by serialization and deserialization) or proxied (by inheriting from MarshalByRefObject) instead. The other AppDomain that this build-in web server uses doesn’t have access to types declared in our assembly (.NET tries to load it, but it looks only in process working direcotry and in GAC, and throws SerializationException. Possible solutions are: inheriting from MarshalByRefObject, installing assembly to GAC, coping it to web server working directory, trying to reset thread principal or using DEVPATH.
All solutions to this problem have some drawback so I decided to just drop my custom IIdentity implementation. I wanted to quickly fix this bug and not stopping my colleague soI created some static methods on my authentication module to provide the additional data that I put before in my IIdentity implementation. In my guts I felt that those static methods aren’t the best solution, but I quickly committed this change to start working on other things.
Lesson learned
Many times I heard and even say to others that code quality is very important, that in long term it always pays off to take care of design, coding standards, refactoring and testing. Software development is about managing complexity, and without paying attention to quality, great project can become real mess, a mountain of complex and intangible code. Great metaphor that nicely describes problems with quality in software projects is Technical Debt. Another highly recommended read!
It’s easy to say “quality is important, always do things the right way, not the quick-and-dirty one”, but it’s a lot harder to do it actually. This time I managed to stop and think about better way of coding this particular feature and after a bit of work I ended with solution that I wan't be ashamed of. The point is, I need always to thing about quality, about design. It’s so easy to start running and doing things the quickest way. But at the end there will always be a moment when you will have to pay back your technical debt, in long term it will always slow down development.
23607dad-a527-4f94-8538-63c22bfe11f3|0|.0