I am working on an MVC 4 intranet application and am using Windows authentication. I would like to add to the user object that the authentication method uses (@User) and get that data from active directory (such as email, phone number, etc).
I know I can create a custom Authorize attribute and add it to the controller that all of my other controllers inherit from, but I don't know if this is the right method to do what I want.
My end goal is simple, I want @User object to have additional properties that are populated via Active Directory. Thanks for any help you can offer.
I was just about to add my own question to StackOverflow with my solution to help others with this issue, when I saw your existing question. It seems like this would be a very common thing, but the information about how to do it only is spread out between multiple sources and hard to track down. There's not just one complete resource, so hopefully this will help you and others.
The best way to do this is use a UserPrincipal extension. Basically, you're subclassing UserPrincipal
from System.DirectoryServices.AccountManagement
and adding your own additional properties. This is enabled via the ExtensionGet
and ExtensionSet
(somewhat magical) methods.
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")]
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context)
{
}
public UserPrincipalExtended(PrincipalContext context, string samAccountName, string password, bool enabled)
: base(context, samAccountName, password, enabled)
{
}
[DirectoryProperty("title")]
public string Title
{
get
{
if (ExtensionGet("title").Length != 1)
return null;
return (string)ExtensionGet("title")[0];
}
set
{
ExtensionSet( "title", value );
}
}
[DirectoryProperty("department")]
public string Department
{
get
{
if (ExtensionGet("department").Length != 1)
return null;
return (string)ExtensionGet("department")[0];
}
set
{
ExtensionSet("department", value);
}
}
public static new UserPrincipalExtended FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalExtended)FindByIdentityWithType(context, typeof(UserPrincipalExtended), identityValue);
}
public static new UserPrincipalExtended FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalExtended)FindByIdentityWithType(context, typeof(UserPrincipalExtended), identityType, identityValue);
}
}
The two attributes on the class need to be customized to your instance of AD. The value for DirectoryRdnPrefix
needs to be the RDN (relative distinguished name) in AD, while the value for DirectoryObjectClass
needs to be the directory object type name in AD for a userObject class. For a typical AD Domain Services setup, they should both be as used in the code presented above, but for an LDS setup, they could be different. I've added two new properties that my organization uses, "title" and "department". From that, you can get an idea of how to add any other property you like: basically you just create a property using the template I've provided here. The property can be named anything you like, but the string value passed to DirectoryProperty
and inside the code block should match up to a property name from AD. With that in place, you can use PrincipalContext
with your subclass instead of UserPrincipal
to get back a user object with the properties you need added.
UserPrincipalExtended user = UserPrincipalExtended.FindByIdentity(
new PrincipalContext(ContextType.Domain), User.Identity.Name);
And access your property like any other on the UserPrincipal
instance:
// User's title
user.Title
If you're unfamiliar with System.DirectoryServices.AccountManagement.UserPrincipal
, there's a few user properties baked in: GivenName
, Surname
, DisplayName
, etc. In particular to your circumstance, since you mentioned phone and email specifically, there's VoiceTelephoneNumber
and EmailAddress
. You can see the full list in the MSDN docs. If all you need is the built-in information, you don't need to extend UserPrincipal
as I showed above. You would just do:
UserPrincipal user = UserPrincipal.FindByIdentity(
new PrincipalContext(ContextType.Domain), User.Identity.Name);
But, 9 times out of 10, the built-ins won't be enough, so it's good to know how to get the rest easily.
Finally, I didn't want to have to add @using
lines to any view that uses this, so I went ahead and added the namespaces to my Views
folder's web.config. That part is important, it needs to be added to the Views
folder's web.config, not the project's (and each Area's individual Views
folder if you're utilizing Areas).
<system.web.webPages.razor>
...
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
...
<add namespace="System.DirectoryServices.AccountManagement" />
<add namespace="Namespace.For.Your.Extension" />
</namespaces>
</pages>
</system.web.webPages.razor>