Cross Domain Authentication using DirectoryServices

13,552

Solution 1

I have come up with this solution to the problem.

In order to support multiple domains, either in trust relationships or even in isolated networks, first of all I added a NameValueCollection in my web.config to list the domains and their domain controllers.

  <domains>
    <add key="domain1" value="10.0.0.1"/>
    <add key="domain2" value="10.0.1.11"/>
  </domains>

(more info on the configuration addition in this so question)

Then the next step was to read the domain from the User's credentials in the way I mention in the question. Having gotten the domain I try to lookup the according domain controller from the configuration values, in order to get the proper LDAP connection string. So my method is this:

private string GetLDAPConnection(string a_Domain, string a_Username, string a_Password)
{
    // Get the domain controller server for the specified domain
    NameValueCollection domains = (NameValueCollection)ConfigurationManager.GetSection("domains");
    string domainController = domains[a_Domain.ToLower()];

    string ldapConn = string.Format("LDAP://{0}/rootDSE", domainController);

    DirectoryEntry root = new DirectoryEntry(ldapConn, a_Username, a_Password);
    string serverName = root.Properties["defaultNamingContext"].Value.ToString();
    return string.Format("LDAP://{0}/{1}", domainController, serverName);
}

Once I get back the proper connection string I make a new call in order to authenticate the user, by addressing the proper LDAP

    ...
    string ldapConn = GetLDAPConnection(domain, username, a_Password);                             
    DirectoryEntry entry = new DirectoryEntry(ldapConn, username, a_Password);        

    try
    {
        try
        {
            object obj = entry.NativeObject;
        }
        catch(DirectoryServicesCOMException comExc)
        {
            LogException(comExc);
            return false;
        }

        DirectorySearcher search = new DirectorySearcher(entry);
        search.Filter = string.Format("(SAMAccountName={0})", username);
        search.PropertiesToLoad.Add("cn");
        SearchResult result = search.FindOne();

From this point on I can also perform all the other queries I want such as the user's group membership etc.

Since the call to the remote domains needs to be bound to a user, I use the "calling" users credentials. This way the user get's authenticated and the Call is bound to the specific user. Furthermore, I specify a "default" domain, for cases where users provide their credentials without specifying the domain.

I did not manage to this however using the PrincipalContext as I wanted, but on the bright side, this solution is also applicable for older .NET 2.0 applications as well.

I am not sure that this is the best solution to the problem, however it seems to work in the tests we have so far performed.

Solution 2

I don't know why I got downvoted but here's what I think might be wrong, the trust level between the server/domain on which you code is hosted and the domain to which you are trying to contact might not be established. I can't provide you reasons why that might be happening.

[EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]

You can try adding this above your function and see if it helps you to go through, but apart from that I don't see why would it be wrong to search on WinNT domain for all the possible users. Hope this helps

Share:
13,552
Nikos Steiakakis
Author by

Nikos Steiakakis

Software Development enthusiast, currently serving as a software developer (and co-founder) for Geosysta, developing various types of apps, and Geotechpedia ...also developing various custom apps on the side just for the fun of it!

Updated on June 04, 2022

Comments

  • Nikos Steiakakis
    Nikos Steiakakis about 2 years

    I need to create a method for my intranet web application that will authenticate a user using DirectoryServices, either against a default domain, or a user specified one.

    On my login form the user will be able to either give there credentials in the form of "username" and "password" or "domain\username" and "password" The first case can be used when the user is in the same domain as the webserver and is quite straightfoward. The code I use is:

     string domain = "";
     // Code to check if the username is in form of "domain\user" or "user"
     string username = ParseUsername(username, out domain);
     if(domain == "")
        domain = defaultDomain;
    
     PrincipalContext context = new PrincipalContext(ContextType.Domain, domain, username, password); 
     bool IsAuthenticated = context.ValidateCredentials(username, password)
    

    I pass the username and password to the PrincipalContext constructor in order to bind the call in cases where I try to access another domain.

    For the local domain the code works fine. However when I try to check against another domain that is being specified through the username, then I get a "Server could not be contacted" error.

    I also tried using different ContextOptions such as ContextOptions.SimpleBind or ContextOptions.Negotiatebut I always seem to be getting the same result.

    I need to implement this, since the application is being shipped to various customers, with either single domain or multiple domain environments. Is there something else I should specify in cases of "remote" domains? The code needs to be flexible since this will be deployed in various environments.

    Thanks

    EDIT: I must point out, that I prefer to do it using DirectoryServices.AccountManagement and PrincipalContext in order to take advantage of other functionality it provides as well.

    Also, I must mention that for my tests, my Dev machine is on a 10.0.0.* network and the second domain I test against is on a 10.0.1.*. I have a route and all, and I can succesfuly connect using an ldap client, so the question is why I cannot connect to the domain via my asp.net application.

  • Nikos Steiakakis
    Nikos Steiakakis over 12 years
    Security Restrictions are my first guess as well. However, in an older implementation of the method, I used to do it with DirectoryEntry and DirectorySearcher of older .NET 2.0 libraries. In that case, I needed to pass the username and password in order to bind the call and that would solve the double hop issue. Now, I just don't seem to connect at all. The main difference now is that I need to allow the users to specify the domain which they belong to, so as to be more versatile.
  • gizgok
    gizgok over 12 years
    I had a similar problem when the place I worked decided to migrate to a new domain. During the migration process we needed to keep both domains active. Therein I found that there were trust issues but could never figure out a solution to it. Instead I went with the WinNT approach where I would search for the userID over the entire network, get that object and then do manipulation as I needed. Also did a latency check for both methods to see if it WinNT was feasible and it turned out to be decent choice.
  • gizgok
    gizgok over 12 years
    @NikosSteiakakis If you figure out the solution do write it here.
  • Nikos Steiakakis
    Nikos Steiakakis over 12 years
    Thank you for your contribution. Unfortunately your suggestion didn't work for me, because I want this to work in cases where the domains might not even have a common global catalog.