0

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
Jonathan
  • 21
  • 3
  • I still suspect that the problem is the identity of the application pool. Even if the principalContext you create uses a valid user name and password in the domain, but the application pool does not have the authority in the domain, the principalContext cannot search for users in the domain. The principalContext can verify to the domain to ensure that it can enter the domain, but to perform a search operation in the domain, the application pool needs to have administrator rights in the domain. So you need to give the application pool the identity of the domain administrator. – Bruce Zhang Nov 26 '20 at 06:42
  • @BruceZhang Why does it work from my development machine in that case? It's not even on the same domain. – Jonathan Nov 30 '20 at 13:41
  • When you use visual studio to develop, the application runs in IIS Express. Compared with the virtual identity ApplicationPoolIdentity created by IIS, the identity of the IIS Express application pool has higher authority, because it must ensure that even if the development is a standard user, it can be completed the vast majority of tasks. [IIS Exress and IIS](https://learn.microsoft.com/en-us/iis/extensions/introduction-to-iis-express/iis-express-overview#iis-express-and-iis) – Bruce Zhang Dec 01 '20 at 09:29
  • @BruceZhang Even when the IIS Express instance isn't on the same domain as the one authentication is being done against? My development system is on domain "A" while the staging IIS and domain server are part of domain "B" and there is no trust relationship of any kind between "A" and "B". I get that IIS Express may have more privileges on the local machine but I don't see how in this case it would have any more rights in domain "B". – Jonathan Dec 01 '20 at 13:20
  • Do you use VPN in development machine? If you use VPN, the development machine is also in the domain. – Bruce Zhang Dec 02 '20 at 09:02
  • That puts it on the same routable network but to my knowledge a VPN handles routing network packets it doesn't establish or force a trust between different domains which would be required to meet your "needs to have administrator rights..." statement. – Jonathan Dec 02 '20 at 13:55
  • Using vpn will set all requests as part of the domain, so you use VPN on the development machine? – Bruce Zhang Dec 03 '20 at 09:55
  • When you say domain do you mean the IP network domain, the DNS domain, or the Active Directory Auth domain? A VPN definitely affects the first, most of the time affects the second, but to my knowledge doesn't force trust across AD domains. If you could point me to documentation that shows that it does I would appreciate it. – Jonathan Dec 03 '20 at 14:15
  • VPN will affect AD, but such documents are written by VPN providers. In fact, many multinational companies use VPN in order to enable devices working in certain areas to access the company’s AD domain without violating the local network rules. They all use VPN. – Bruce Zhang Dec 04 '20 at 09:35
  • @Jonathan, did it fix for you? – User16119012 Jan 18 '21 at 17:55
  • I have not gotten this working. – Jonathan Jan 18 '21 at 21:07
  • @User16119012 I figured it out and posted an answer. – Jonathan Feb 12 '21 at 20:17

1 Answers1

0

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.”””

https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.accountmanagement.principalcontext.-ctor?view=dotnet-plat-ext-5.0#System_DirectoryServices_AccountManagement_PrincipalContext__ctor_System_DirectoryServices_AccountManagement_ContextType_System_String_System_String_System_String_

Jonathan
  • 21
  • 3