Making ASP.NET MVC Windows Authentication With Role-Based Security Painless for Developers

I’ve always been a bit of a Windows Authentication hater for all the wrong reasons.  If I want to change my roles, I have to change my group memberships in Active Directory.  The network administrator never wants to give me access so I have to make a request and wait.  In the alternative, I can get a number of different users setup and logon to the computer as one of them every time I want to test different security settings.  If my development box is not part of the domain, I can manipulate my local security settings, but that is not always an option either.  I can also set things up so my account doesn’t have access to the application.  This way I “simply” type in the credentials of the user I want to test under each time I fire up the web application. It is just painful to work with Windows Authentication on a developer box

Unfortunately, my current team has to deal with Windows Authentication on our current project and was running into these headaches on a daily basis.  I finally put together a simple solution that swaps in a configuration-based GenericPrincipal for developers that allows them to set up any role memberships they want locally without impacting Active Directory or the server deployment environment.  It’s all controlled from a single configuration setting contained in the appSettings section of the web.config:

<!-- Comma-delimited list of roles to be used in development environment (Windows authentication off).-->
<add key="Roles" value="AutobahnAdministrators" />

If the configuration key is present, the AutenticateRequest event in global.asax injects a GenericPrincipal with the configured role memberships:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    var roles = ConfigurationManager.AppSettings["Roles"];
    if (!string.IsNullOrEmpty(roles))
    {
        var genericIdentity = new GenericIdentity("developer");
        HttpContext.Current.User = new GenericPrincipal(genericIdentity, roles.Split(','));
    }
}

Optional — Allow IoC Container to Inject Correct IPrincipal

Our team uses Castle Windsor to inject an IPrincipal into our controllers to perform role checks. This allows us to avoid directly accessing the HttpContext, which makes unit testing much easier. I’m sure the code below could be adapted to work with any of the popular IoC libraries.

public class Installer : IWindsorInstaller
{
    protected override void RegisterComponents(IWindsorContainer container, IConfigurationStore store)
    {
        //This allows us to inject an IPrincipal into any controller.
        //Will be based on configuration if:
        //  1) Anonymous authentication is enabled (Windows authentication is disabled)
        //  2) AppSettings["Roles"] is a comma-delimited list of roles to assign
        //
        // Throws exception if anonymous authentication is in use and AppSettings/@key=Roles is not configured
        container.Register(Component.For().UsingFactoryMethod((kernel, creationContext) =>
            {
                //if this is a generic principal, it was injected based on the configuration so we can just use it
                if (HttpContext.Current.User is GenericPrincipal)
                {
                    return HttpContext.Current.User;
                }
                //This is only used in integration test scenarios using the container.  If you are running the web app, the proper generic identity from the web.config was injected in global.asax AuthenticateRequest()
                var roles = ConfigurationManager.AppSettings["Roles"];
                if (!string.IsNullOrEmpty(roles))
                {
                    return new GenericPrincipal(new GenericIdentity("developer"), roles.Split(','));
                }

                //if there is not configuration and this is a windows principal, then just return it
                if (HttpContext.Current.User is WindowsPrincipal)
                {
                    return HttpContext.Current.User;
                }

                throw new HttpException(401, "Windows authentication must be on or AppSettings/@key=Roles must be configured");
            }).LifestylePerWebRequest());
    }
}

2 responses

  1. I REALLY REALLY DO NOT LIKE THIS. This just screams gaping security hole.

    <add key=”Roles” value=”AutobahnAdministrators” />

    accidentally gets checked into the web.config. This gets deployed to staging, people who review staging very well likely be in the group AutobahnAdministrators, so they notice nothing different. Then this code goes to production. Now you’ve given everyone administrator access. GG.

    I also really dislike the concept that your security is baked into IoC registration, that’s even larger of a gaping hole in the SRP than the bypass concept puts into security.

    Security needs to be nearly impossible to screw up, it should require the minimal developer action as possible. Security should be applied globally. HttpModules are the best candidate as they are extremely agnostic of all things and globally applied. You can use the DynamicModuleUtility and WebActivator to configure those through code or in the web.config.

    I have a construct similar to your bypass but it has multiple layers of protection to prevent it from ever making it to production. It requires configuration you opt into to mark it’s debug mode (independent of ASP.NET debug configuration and build configuration) and then everything is wrapped in #IF DEBUG so if the application would ever get deployed it will immediately and totally fail in production (and staging). Fail fast.

    If you extracted a class from RegisterComponents and it was more along the lines RegisterComponents { MyUserProvider.User } that would be better to end the SRP violation, then if you wrap #IF DEBUG around all ConfigurationManager.AppSettings[“Roles”]; related code. That massively reduces the risk surface you’re exposing with development advocated in this post.

    • Your point is well taken. I’ve done more complex and secure code implementation in the past myself. However, in our case it doesn’t matter what gets into the web.config in source control because we set that configuration key (and many others) via rules in our automated deployment. That let me minimize the implementation effort by keeping the code extremely simple. If I was doing manual deploys or deploying the source code version of web.config, I’d be more worried.

      A fully automated, repeatable and idiot-proof deployment process for test, staging and production environments is a must have in any serious agile project. We use Octopus Deploy and recommend it highly.

%d bloggers like this: