Tijmen 10:57, 2 September 2009

Every now and then I want to do a simple authentication scheme in an ASP.NET web application, without calling on various providers or other components that bring too much weight & functionality to the table. Implementing a custom IIdentity and IPrincipal is a straightforward way of achieving this.

The steps involved:

  • create a custom implementation of these two interfaces
  • set the custom principal as the user for HttpContext.Current
  • cache the principal in session state (not actually required, but a simple optimization)

Implementing the interfaces

A simple skeleton for the IIdentity implementation:

public class MyIdentity : IIdentity
{
    private string passedInName; 

    // IIdentity
    public string AuthenticationType { get { return "MyIdentityAuthenticationType"; } }
    public string Name { get { return passedInName; } }

    public bool IsAuthenticated
    {
        get
        {
            // TODO: add whatever logic we need here (for instance, AD group membership)
            throw new NotImplementedException();
        }
    }
    // /IIdentity

    // Hide constructor
    private MyIdentity() { }

    // Static factory method
    internal static IIdentity CreateIdentity(IUserNameProvider provider)
    {
        IIdentity identity = new MyIdentity { passedInName = provider.AccountName } ;
        return identity;
    }
}

And for the IPrincipal-implementing class:

public class MyPrincipal : IPrincipal
{
    private IIdentity identity;

    // IPrincipal
    public IIdentity Identity    { get { return identity; } }
    public bool IsInRole(string role)
    {
        // TODO add logic here
        throw new NotImplementedException();
    }

    // hide Ctor
    private MyPrincipal() { }

    // static factory method
    public static IPrincipal CreatePrincipal(IUserNameProvider provider)
    {
        IIdentity identity = MyIdentity.CreateIdentity(provider);
        IPrincipal principal = new MyPrincipal { identity = identity };
        return principal;
    }
}

A few things to note:

  • IUserNameProvider has only a single string property, name. The injected type is used to determine the current user's name.
  • I used simple static factory methods, that are by no means necessary in this context (regular constructors would work just as well).
  • you could factor out the two "business logic" methods IsAuthenticated and IsInRole to separate concerns using the template method pattern, but this is supposed to be a simple, lean implementation.

Hooking them up

The easiest way I've found to link the custom principal is by implementing the Application_AcquireRequestState event in the global.asax:

protected void Application_AcquireRequestState(object sender, EventArgs e)
{
    IPrincipal principal = null;

    var current = HttpContext.Current;

    // check if session is available; needed because this event can/does fire multiple times per request
    if (current.Session != null)
    {
        // check if we have the principal in session
        if (current.Session["magicstring"] != null)
        {
            principal = current.Session["magicstring"] as IPrincipal;
        }
        // catches both (1) key not in session and (2) cast not succesfull
        if (principal == null)
        {
            // create a new one and store it in the session
            principal = MyPrincipal.CreatePrincipal(new MyWebAccountNameProvider());
            current.Session["magicstring"] = principal;
        }

        // regardless of source (session or freshly created, store in appropriate context)
        current.User = principal;
    }
}

Actually, this event is a bit weird, because it can fire multiple times for a single request. The Session reference is only valid once however, which is why that check is there. So, what happens:

  1. The existence of a session variable is checked, and, if it exists, it is cast to an IPrincipal.
  2. If either the cast failed or the session var was empty, a new specific principal is constructed and stored in session.
  3. The custom principal is linked to the HttpContext.Current.User property, allowing us to use it in the rest of the application.

Wrapup and comments

This is by no means a do-all, end-all solution, but it works well. Depending on the tiering/layering of the solution, you can either check the HttpContext.Current.User property, or you can abstract that into a simple wrapper. I usually either use a static method on my principal that returns the actual active principal, or set up a context registry to hold my principal reference transparently across layers.