# Active Directory Authentication This will provide an example of integrating Active Directory authentication in an ASP.NET Core app. > Note, you'll need to be running on a Windows domain with Visual Studio debugging in IIS Express for this to work. ## Setup In `launchSettings.json`, you'll want to modify `iisSettings` by turning on `windowsAuthentication`: **`launchSettings.json`** ``` json { "iisSettings": { "windowsAuthentication": true, "anonymousAuthentication": false, "iisExpress": { "applicationUrl": "http://localhost:5000" } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "FullstackOverview.Web": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ``` ## Identity Project Create a `netcoreapp2.2` class library (I tend to name mine `{Project}.Identity`). You'll need to add the following NuGet packages to this library: * Microsoft.AspNetCore.Http * Microsoft.Extensions.Configuration.Abstractions * Microsoft.Extensions.Configuration.Binder * System.DirectoryServices * System.DirectoryServices.AccountManagement Here is the infrastructure of this class library: * **Extensions** * IdentityExtensions.cs * MiddlewareExtensions.cs * AdUser.cs * AdUserMiddleware.cs * AdUserProvider.cs * IUserProvider.cs **`AdUser.cs`** I use this class so I can create a Mock implementation of this library for when I'm building outside of a domain environment. This relieves me of the dependency on `UserPrincipal`. ``` cs using System; using System.DirectoryServices.AccountManagement; using System.Linq; using System.Security.Principal; using System.Threading.Tasks; namespace Project.Identity { public class AdUser { public DateTime? AccountExpirationDate { get; set; } public DateTime? AccountLockoutTime { get; set; } public int BadLogonCount { get; set; } public string Description { get; set; } public string DisplayName { get; set; } public string DistinguishedName { get; set; } public string Domain { get; set; } public string EmailAddress { get; set; } public string EmployeeId { get; set; } public bool? Enabled { get; set; } public string GivenName { get; set; } public Guid? Guid { get; set; } public string HomeDirectory { get; set; } public string HomeDrive { get; set; } public DateTime? LastBadPasswordAttempt { get; set; } public DateTime? LastLogon { get; set; } public DateTime? LastPasswordSet { get; set; } public string MiddleName { get; set; } public string Name { get; set; } public bool PasswordNeverExpires { get; set; } public bool PasswordNotRequired { get; set; } public string SamAccountName { get; set; } public string ScriptPath { get; set; } public SecurityIdentifier Sid { get; set; } public string Surname { get; set; } public bool UserCannotChangePassword { get; set; } public string UserPrincipalName { get; set; } public string VoiceTelephoneNumber { get; set; } public static AdUser CastToAdUser(UserPrincipal user) { return new AdUser { AccountExpirationDate = user.AccountExpirationDate, AccountLockoutTime = user.AccountLockoutTime, BadLogonCount = user.BadLogonCount, Description = user.Description, DisplayName = user.DisplayName, DistinguishedName = user.DistinguishedName, EmailAddress = user.EmailAddress, EmployeeId = user.EmployeeId, Enabled = user.Enabled, GivenName = user.GivenName, Guid = user.Guid, HomeDirectory = user.HomeDirectory, HomeDrive = user.HomeDrive, LastBadPasswordAttempt = user.LastBadPasswordAttempt, LastLogon = user.LastLogon, LastPasswordSet = user.LastPasswordSet, MiddleName = user.MiddleName, Name = user.Name, PasswordNeverExpires = user.PasswordNeverExpires, PasswordNotRequired = user.PasswordNotRequired, SamAccountName = user.SamAccountName, ScriptPath = user.ScriptPath, Sid = user.Sid, Surname = user.Surname, UserCannotChangePassword = user.UserCannotChangePassword, UserPrincipalName = user.UserPrincipalName, VoiceTelephoneNumber = user.VoiceTelephoneNumber }; } public string GetDomainPrefix() => DistinguishedName .Split(',') .FirstOrDefault(x => x.ToLower().Contains("dc")) .Split('=') .LastOrDefault() .ToUpper(); } } ``` **`IUserProvider.cs`** I use this interface so that I can create an additional provider in a mock library that implements this interface so I don't have to be connected to an AD domain while at home. ``` cs using System; using System.Collections.Generic; using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; namespace Project.Identity { public interface IUserProvider { AdUser CurrentUser { get; set; } bool Initialized { get; set; } Task Create(HttpContext context, IConfiguration config); Task GetAdUser(IIdentity identity); Task GetAdUser(string samAccountName); Task GetAdUser(Guid guid); Task> GetDomainUsers(); Task> FindDomainUser(string search); } } ``` **`AdUserProvider.cs`** Because you're using Windows authentication, the `HttpContext` will contain an `IIdentity` of the user logged into the domain that is accessing the web app. Because of this, we can leverage the `System.DirectoryServices.AccountManagement` library to pull their `UserPrincipal`. ``` cs using System; using System.Collections.Generic; using System.DirectoryServices.AccountManagement; using System.Linq; using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Project.Identity.Extensions; namespace Project.Identity { public class AdUserProvider : IUserProvider { public AdUser CurrentUser { get; set; } public bool Initialized { get; set; } public async Task Create(HttpContext context, IConfiguration config) { CurrentUser = await GetAdUser(context.User.Identity); Initialized = true; } public Task GetAdUser(IIdentity identity) { return Task.Run(() => { try { PrincipalContext context = new PrincipalContext(ContextType.Domain); UserPrincipal principal = new UserPrincipal(context); if (context != null) { principal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, identity.Name); } return AdUser.CastToAdUser(principal); } catch (Exception ex) { throw new Exception("Error retrieving AD User", ex); } }); } public Task GetAdUser(string samAccountName) { return Task.Run(() => { try { PrincipalContext context = new PrincipalContext(ContextType.Domain); UserPrincipal principal = new UserPrincipal(context); if (context != null) { principal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, samAccountName); } return AdUser.CastToAdUser(principal); } catch (Exception ex) { throw new Exception("Error retrieving AD User", ex); } }); } public Task GetAdUser(Guid guid) { return Task.Run(() => { try { PrincipalContext context = new PrincipalContext(ContextType.Domain); UserPrincipal principal = new UserPrincipal(context); if (context != null) { principal = UserPrincipal.FindByIdentity(context, IdentityType.Guid, guid.ToString()); } return AdUser.CastToAdUser(principal); } catch (Exception ex) { throw new Exception("Error retrieving AD User", ex); } }); } public Task> GetDomainUsers() { return Task.Run(() => { PrincipalContext context = new PrincipalContext(ContextType.Domain); UserPrincipal principal = new UserPrincipal(context); principal.UserPrincipalName = "*@*"; principal.Enabled = true; PrincipalSearcher searcher = new PrincipalSearcher(principal); var users = searcher .FindAll() .AsQueryable() .Cast() .FilterUsers() .SelectAdUsers() .OrderBy(x => x.Surname) .ToList(); return users; }); } public Task> FindDomainUser(string search) { return Task.Run(() => { PrincipalContext context = new PrincipalContext(ContextType.Domain); UserPrincipal principal = new UserPrincipal(context); principal.SamAccountName = $"*{search}*"; principal.Enabled = true; PrincipalSearcher searcher = new PrincipalSearcher(principal); var users = searcher .FindAll() .AsQueryable() .Cast() .FilterUsers() .SelectAdUsers() .OrderBy(x => x.Surname) .ToList(); return users; }); } } } ``` **`AdUserMiddleware.cs`** Custom middleware for creating the `IUserProvider` instance registered with Dependency Injection (see **Startup Configuration** below). ``` cs using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; namespace Project.Identity { public class AdUserMiddleware { private readonly RequestDelegate next; public AdUserMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context, IUserProvider userProvider, IConfiguration config) { if (!(userProvider.Initialized)) { await userProvider.Create(context, config); } await next(context); } } } ``` **`IdentityExtensions.cs`** Utility extensions for only pulling users with a Guid, and casting `UserPrincipal` to `AdUser`. ``` cs using System.DirectoryServices.AccountManagement; using System.Linq; namespace Project.Identity.Extensions { public static class IdentityExtensions { public static IQueryable FilterUsers(this IQueryable principals) => principals.Where(x => x.Guid.HasValue); public static IQueryable SelectAdUsers(this IQueryable principals) => principals.Select(x => AdUser.CastToAdUser(x)); } } ``` **`MiddlewareExtensions.cs`** Utility extension for making middleware registration in `Startup.cs` easy. ``` cs using Project.Identity; namespace Microsoft.AspNetCore.Builder { public static class MiddlewareExtensions { public static IApplicationBuilder UseAdMiddleware(this IApplicationBuilder builder) => builder.UseMiddleware(); } } ``` ## Startup Configuration To access the current user within the application, in the `Startup.cs` class of your ASP.NET Core project, you need to register an `IUserProvider` of type `AdUserProvider` with Dependency Injection with a Scoped lifecycle (per HTTP request): ``` cs public void ConfigureServices(IServiceCollection services) { // Additional service registration services.AddScoped(); // Additional service registration } ``` You then need to add the `AdUserMiddleware` to the middleware pipeline: ``` cs public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // Additional Configuration app.UseAdMiddleware(); // Additional Configuration } ``` ## Accessing the Current User Because the `IUserProvider` is configured in the middleware pipeline, and is registered with Dependency Injection, you can setup an API point to interact with the registered instance: **`IdentityController.cs`** ``` cs [Route("api/[controller]")] public class IdentityController : Controller { private IUserProvider provider; public IdentityController(IUserProvider provider) { this.provider = provider; } [HttpGet("[action]")] public async Task> GetDomainUsers() => await provider.GetDomainUsers(); [HttpGet("[action]/{search}")] public async Task> FindDomainUser([FromRoute]string search) => await provider.FindDomainUser(search); [HttpGet("[action]")] public AdUser GetCurrentUser() => provider.CurrentUser; } ```