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.
Answer:
Essentially it’s translation issues between
WindowsIdentityandNTAccount(both of these System.Security.Principal) and lastly, the actual Active Directory entry.When validating a
WindowsIdentityagainst AD, if you want to use anything other than the Sam or the Sid, you will need to useSystem.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.Useris aWindowsPrincipalobject wrapping an underlyingWindowsIdentity.WindowsIdentityhas for most intents and purposes only two properties with which the authenticated user can be identified:NameandUser.These properties translate to two properties on the identity’s corresponding AD account entry:
WindowsIdentity.Name=SamAccountNameWindowsIdentity.User=SIDThe
[Authorize]filter attribute ultimately callsIsInRole(string role)on the underlying principal… and theIsInRole()string overload instantiates anNTAccountwith therole(the “SamAccountName” in an AD entry).This explains the failure in #1 and #2 above.
To authorize the
HttpContext.Useragainst anything but his/her Sid or SamAccountName, you’ll needDirectoryServices.AccountManagementor classic LDAP.