Active Directory Authentication with OWIN in MVC5. Part 2: Roles and Corrections

My first blog post about AD authentication proven to be very popular – amount of visits to this post in the last month have beaten the previous all-popular post about HTTPS in MVC and even about configuring Dependency Injection with Identity. So I decided to write a follow-up with clarifications and corrections.

Guest account and security issue

I’ve been working with that code for a while now and it was in production since start of the year. However I have discovered an unsettling security issue. The issue was not exposed in my sample code – it was covered by try-catch expression, but for a wrong reason. I’d like to correct the issue here.

In the sample code for authentication I’m using code like this: isAuthenticated = principalContext.ValidateCredentials(username, password, ContextOptions.Negotiate);. But it turns out that ValidateCredentials will return true for an unknown user if Guest account is enabled in the system. Why does it do it – beyond me; I did look into source code of System.DirectoryServices.AccountManagement module and it looks like the problem is actually with Active Directory replies rather than with .Net library that deals with AD.

To prevent this issue affecting our code in any way, in ActiveDirectoryAuthentication.cs starting from line #41 replace code with this:

try
{
    userPrincipal = UserPrincipal.FindByIdentity(principalContext, username);
    if (userPrincipal != null)
    {
        isAuthenticated = principalContext.ValidateCredentials(username, password, ContextOptions.Negotiate);
    }
}
catch (Exception exception)
{
    //TODO log exception in your ELMAH like this:
    //Elmah.ErrorSignal.FromCurrentContext().Raise(exception);
    return new AuthenticationResult("Username or Password is not correct");
}

This basically swaps the 2 queries – first verify that account exists, and if it exists check that password is valid. And really the exception handling is not needed here – I’ve not seen an exception been thrown there ever (given production system with about 2000 users daily). But better keep it for a good measure -)))

Roles

Multiple users left comments asking how roles should be applied and why do they not work. Well.. I did not need roles so I did not implement them, but they are pretty simple. In AdAuthenticationService.CreateIdentity() method all you need to add is this:

var groups = userPrincipal.GetAuthorizationGroups();
foreach (var @group in groups)
{
    identity.AddClaim(new Claim(ClaimTypes.Role, @group.Name));
}

The entire method will be:

    private ClaimsIdentity CreateIdentity(UserPrincipal userPrincipal)
    {
        var identity = new ClaimsIdentity(MyAuthentication.ApplicationCookie, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
        identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Active Directory"));
        identity.AddClaim(new Claim(ClaimTypes.Name, userPrincipal.SamAccountName));
        identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userPrincipal.SamAccountName));
        if (!String.IsNullOrEmpty(userPrincipal.EmailAddress))
        {
            identity.AddClaim(new Claim(ClaimTypes.Email, userPrincipal.EmailAddress));
        }

        var groups = userPrincipal.GetAuthorizationGroups();
        foreach (var @group in groups)
        {
            identity.AddClaim(new Claim(ClaimTypes.Role, @group.Name));
        }

        // add your own claims if you need to add more information stored on the cookie

        return identity;
    }

Then you’ll be able to restrict access to controllers by good-old Authorize attribute on top of your controllers or actions: [Authorize(Roles = "Users")]. To check if this works – add role authorization to some role that does not exist: [Authorize(Roles = "NonExistingRole")] – you should be redirected to the login screen.

If you are migrating your project from MembershipProvider – you might get SQL connection exception here – for these cases check if you have something about RoleProvider in your web.config.

Samples

As per usual, I’ve updated the code samples in github repository and added some more useful bits. If you are the “show-me-the-codez” type of developer, go to github and download the sample solution with AD authentication implemented and working.