3

Where I work, we have two modes of authentication:

CAS is the primary method, but it is often unreliable at peak traffic times and so we have been using LDAP as a fallback mode for when we notice that CAS is down. Previously, we were using PHP for doing our LDAP fallback and got reasonable performance. There wasn't a noticeable delay during login other than the expected network lag times. A login took probably ~250-500ms to complete using LDAP.

Now, we are making a new system and have chosen ASP.NET MVC4 as the platform rather than PHP and I am tasked with trying to get this fallback working again. I have been pulling my hair out for about 6 hours now trying different things over and over again, getting the same result (perhaps I am insane). I have finally managed to connect to LDAP, authenticate the user, and get their attributes from LDAP. However, the query consistently takes 4.5 seconds to complete no matter what method I try.

This is very surprising to me seeing as the PHP version was able to do nearly the same thing in 1/8th the time and it would seem that the .NET framework has excellent support for LDAP/ActiveDirectory. Am I doing something incredibly horribly wrong?

Here are the guts of my function as it stands now (this one is the latest iteration that manages to do everything in one 4.5 second query):

public Models.CASAttributes Authenticate(string username, string pwd)
{
    string uid = string.Format("uid={0},ou=People,o=byu.edu", username);

    LdapDirectoryIdentifier identifier = new LdapDirectoryIdentifier("ldap.byu.edu", 636, false, false);

    try
    {
        using (LdapConnection connection = new LdapConnection(identifier))
        {
            connection.Credential = new NetworkCredential(uid, pwd);
            connection.AuthType = AuthType.Basic;
            connection.SessionOptions.SecureSocketLayer = true;
            connection.SessionOptions.ProtocolVersion = 3;

            string filter = "(uid=" + username + ")";
            SearchRequest request = new SearchRequest("ou=People,o=byu.edu", filter, SearchScope.Subtree);
            Stopwatch sw = Stopwatch.StartNew();
            SearchResponse response = connection.SendRequest(request) as SearchResponse;
            sw.Stop();
            Debug.WriteLine(sw.ElapsedMilliseconds);
            foreach (SearchResultEntry entry in response.Entries)
            {
                Debug.WriteLine(entry.DistinguishedName);
                foreach (System.Collections.DictionaryEntry attribute in entry.Attributes)
                {
                    Debug.WriteLine(attribute.Key + " " + attribute.Value.GetType().ToString());
                }
                Debug.WriteLine("");
            }
        }
    }
    catch
    {
        Debugger.Break();
    }

    Debugger.Break();
    return null; //debug
}

The PHP version of this follows this sequence:

  1. Bind anonymously and look up the user information using a basedn and cn
  2. Bind again using the username and password of the user to see if they are authentic

It does two binds (connects?) in 1/8th the time it takes the .NET version to do one! Its this sort of thing that makes me thing I am missing something.

I have tried methods based on the following sites:

EDIT:

Using wireshark, I saw that the following requests are made:

  1. bindRequest passing along my uid (delta 0.7ms)
  2. bindResponse success (delta 2ms)
  3. searchRequest "ou=People,o=byu.edu" wholdSubtree (delta 0.2ms)
  4. searchResEntry "uid=my uid,ou=People,o=byu.edu" | searchResDone success 1 result (delta 10.8ms)
  5. unbindRequest (delta 55.7ms)

Clearly, the overhead is coming from .NET and not from the requests. These don't add up to 4.5 seconds in any way, shape, or form.

Community
  • 1
  • 1
Los Frijoles
  • 4,771
  • 5
  • 30
  • 49

2 Answers2

3

I think you're definitely on the right track using System.DirectoryServices for this, so you may just need to tweak your search request a bit.

You're only looking to get one result back here, correct? Set your size accordingly :

request.SizeLimit = 1;

This is a tricky one, but make also sure you're suppressing referral binds as well. You'll want to set this before you call connection.SendRequest(request) :

//Setting the DomainScope will suppress referral binds from occurring during the search
SearchOptionsControl SuppressReferrals = new SearchOptionsControl(SearchOption.DomainScope);
request.Controls.Add(SuppressReferrals);
X3074861X
  • 3,709
  • 5
  • 32
  • 45
  • 1
    Thanks for your suggestion to suppress referral binds. This has reduced my SendRequest time from 21250ms to 35ms! +1 – Chris Walsh Jul 03 '17 at 09:01
3

ldap.byu.edu sure looks like a fully qualified DNS host name. You should change your LdapDirectoryIdentifier constructor to new LdapDirectoryIdentifier("ldap.byu.edu", 636, true, false).

Sean Hall
  • 7,629
  • 2
  • 29
  • 44
  • That eliminated a 3s DNS request I noticed yesterday and fixed the time issue...I should have noticed that flag before. I was stuck using the IP address to get the speed I needed, but now I can switch back to actually using the domain name. Thank you very much! – Los Frijoles Aug 15 '13 at 20:02