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.
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.
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.
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.
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
.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With