The code below works on my development machine when debugging in Visual Studio. The development machine is on a different domain than the staging and production servers but creating the principal context with a username and password seemed to solve the issues I had there.
When run on the staging server ValidateCredentials passes but the FindByIdentity() calls fails with the stack trace below.
The IIS pool is running as ApplicationPoolIdentity but I don't think that should matter as ValidateCredentials works (so contacting the domain controller works) and the PrincipalContext is created with a valid username and password for the domain (so the user the pool runs as shouldn't matter).
I found suggestions to use HostingEnvironment.Impersonate() but I'm pretty sure that's not an option with .Net 5/Core and as I'm passing an explicit username and password to the PrincipalContext it shouldn't be in play anyway.
I found https://stackoverflow.com/a/39118337/ and I can try creating a new app pool and moving the site there but I don't like "black magic" fixes and think it's rather unlikely to work.
using var principalContext = new PrincipalContext(ContextType.Domain, _activeDirectoryConfiguration.ControllerNameOrIp, username, password);
if (!principalContext.ValidateCredentials(username, password))
{
_logger.LogWarning(ErrorMessages.UNABLE_TO_AUTHENTICATE + " {0}", username);
errors.Add("", ErrorMessages.UNABLE_TO_AUTHENTICATE);
return (null, errors);
}
IEnumerable<string> userGroups;
UserPrincipal adUser;
adUser = UserPrincipal.FindByIdentity(principalContext, username);
userGroups = adUser.GetAuthorizationGroups().Select(x => x.Name).ToList();
DirectoryEntry dirEntry = (DirectoryEntry)adUser.GetUnderlyingObject();
string office = dirEntry.Properties["physicalDeliveryOfficeName"].Value.ToString();
Stack Trace:
System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.PropertyValueCollection.PopulateList()
at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName)
at System.DirectoryServices.PropertyCollection.get_Item(String propertyName)
at System.DirectoryServices.AccountManagement.PrincipalContext.DoLDAPDirectoryInitNoContainer()
at System.DirectoryServices.AccountManagement.PrincipalContext.DoDomainInit()
at System.DirectoryServices.AccountManagement.PrincipalContext.Initialize()
at System.DirectoryServices.AccountManagement.PrincipalContext.get_QueryCtx()
at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithTypeHelper(PrincipalContext context, Type principalType, Nullable`1 identityType, String identityValue, DateTime refDate)
at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, IdentityType identityType, String identityValue)
at System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(PrincipalContext context, IdentityType identityType, String identityValue)
at XXX.ActiveDirectoryService.Authenticate(String username, String password) in XXX\Services\ActiveDirectoryService.cs:line 55
at XXX.AuthService.Authenticate(String username, String password) in XXX\Services\AuthService.cs:line 59
Finally figured this one out!
Either passing in an empty string isn’t the same as passing in null or if it’s equivalent then for some reason the sentence below from the MS docs doesn’t actually apply to the this setup even though I thought it did.
I set it to the AD domain base (client.com) and it worked.
“””If the name is null for a Domain context type this context is a domain controller for the domain of the user principal under which the thread is running.”””
User contributions licensed under CC BY-SA 3.0