Active Directory Group Membership Checking in .Net 4.5

.net, active-directory, asp.net-mvc, c#, security

Solution

Answer:

Essentially it's translation issues between `WindowsIdentity` and `NTAccount` (both of these System.Security.Principal) and lastly, the actual Active Directory entry.

When validating a `WindowsIdentity` against AD, if you want to use anything other than the Sam or the Sid, you will need to use `System.DirectoryServices.AccountManagement`.

Caveat: In .Net 4.5 the security principals include Claims but that's out of context.

Long Explanation:

In a Windows Authenticated web application, `HttpContext.User` is a `WindowsPrincipal` object wrapping an underlying `WindowsIdentity`.

`WindowsIdentity` has for most intents and purposes only two properties with which the authenticated user can be identified: `Name` and `User`.

These properties translate to two properties on the identity's corresponding AD account entry:

`WindowsIdentity.Name` = `SamAccountName`

`WindowsIdentity.User` = `SID`

The `[Authorize]` filter attribute ultimately calls `IsInRole(string role)` on the underlying principal... and the `IsInRole()` string overload instantiates an `NTAccount` with the `role` (the "SamAccountName" in an AD entry).

This explains the failure in #1 and #2 above.

To authorize the `HttpContext.User` against anything but his/her Sid or SamAccountName, you'll need `DirectoryServices.AccountManagement` or classic LDAP.

Problem

I have an ASP.Net MVC application using Windows Authentication, and I am checking group membership for security on controller actions. Simple as it sounds, I've found no other Question that can resolve the problem I am experiencing. First Attempt: [Authorize] The classic method is to simply slap an `Authorize` data annotation attribute on the controller action and go to town: ``` [Authorize(Roles = @"domain\groupName1")] ``` No dice. I am prompted for credentials. Usually this means something is wrong with the Windows Authentication configuration but it's setup fine: (1) `HttpContext.User` is a `WindowsPrincipal` object, and (2) I confirmed another known group name works. Second Attempt: IsInRole() The next step taken was to go a more old fashioned route and use `IPrincipal.IsInRole()`, and again, one returns `false`, the other `true`. ``` var wp = (WindowsPrincipal)User; // false var inGroup1 = wp.IsInRole(@"domain\groupName1"); // true var inGroup2 = wp.IsInRole(@"domain\groupName2"); ``` Stumped... so I hit up my systems nerds and we double check everything. User is a group member? Yes. Group name is spelled correctly? Yes. The next step was to snag the SID. Third Attempt: Search Identity's Group Collection In my controller I check the `WindowsIdentity` and look through the group collection for the SID of the troublesome group: ``` var wi = (WindowsIdentity)wp.Identity; var group = wi.Groups.SingleOrDefault(g => g.Value == "group1-sidValue"); ``` The `group` variable is the `SecurityIdentifier` object. Because it is not null, we can be certain that this current user is a member of the group that both the `[Authorize()]` or `IsInRole()` attempts fail to confirm. Fourth Attempt: DirectoryServices.AccountManagement At this point, I'm going nuts and add reference to the AccountManagement APIs. I search the domain context for the `GroupPrincipal` by both name and SID: ``` var pc = new PrincipalContext(ContextType.Domain, "domain"); var gp1byName = GroupPrincipal.FindByIdentity(pc, "groupName1") var gp1bySid = GroupPrincipal.FindByIdentity(pc, IdentityType.Sid, "group1-sidValue"); ``` Both group principal variables are ripe with the same object, and I verified through a watch variable that the principal's `Members` collection contains a `UserPrincipal` object with the same SID as the current `WindowsPrincipal` on `HttpContext`. Question: What in the hell have I missed here? Why would both role checking methodologies fail when it is plain and clear through object exploration that the user is a valid member of this given group? The fact that one group checks fine and the other does not seems the most strange part at this point.

Original source