How do I validate Active Directory creds over LDAP + SSL?

29,073

Solution 1

I was able to validate credentials using the System.DirectoryServices.Protocols namespace, thanks to a co-worker. Here's the code:

// See http://support.microsoft.com/kb/218185 for full list of LDAP error codes
const int ldapErrorInvalidCredentials = 0x31;

const string server = "sd.example.com:636";
const string domain = "sd.example.com";

try
{
    using (var ldapConnection = new LdapConnection(server))
    {
        var networkCredential = new NetworkCredential(_username, _password, domain);
        ldapConnection.SessionOptions.SecureSocketLayer = true;
        ldapConnection.AuthType = AuthType.Negotiate;
        ldapConnection.Bind(networkCredential);
    }

    // If the bind succeeds, the credentials are valid
    return true;
}
catch (LdapException ldapException)
{
    // Invalid credentials throw an exception with a specific error code
    if (ldapException.ErrorCode.Equals(ldapErrorInvalidCredentials))
    {
        return false;
    }

    throw;
}

I'm not thrilled with using a try/catch block to control decisioning logic, but it's what works. :/

Solution 2

Maybe this is another way. There's nothing unusual in validate credentials. The ContextOptions must set properly.

Default value:

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing

Add Ssl:

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing | ContextOptions.SecureSocketLayer

ContextOptions.Negotiate or ContextOptions.SimpleBind is required. Or whatever your server need to perform authentication. ContextOptions only supports OR bit to bit.

You could try also set the ContextOptions directly this way in ValidateCredentials method.

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate | ContextOptions.SecureSocketLayer))
{
    return pc.ValidateCredentials(_username, _password);
}

Or

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
    return pc.ValidateCredentials(_username, _password, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);
}
Share:
29,073
Nate Sauber
Author by

Nate Sauber

Updated on March 16, 2020

Comments

  • Nate Sauber
    Nate Sauber about 4 years

    I'm trying to use the .NET 3.5 System.DirectoryServices.AccountManagement namespace to validate user credentials against our Active Directory LDAP server over an SSL encrypted LDAP connection. Here's the sample code:

    using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:389", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
    {
        return pc.ValidateCredentials(_username, _password);
    }
    

    This code works fine over unsecured LDAP (port 389), however I'd rather not transmit a user/pass combination in clear text. But when I change to LDAP + SSL (port 636), I get the following exception:

    System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests.
      at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
      at System.DirectoryServices.Protocols.LdapSessionOptions.FastConcurrentBind()
      at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions)
      at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
      at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
      at (my code)
    

    Port 636 works for other activities, such as looking up non-password information for that LDAP/AD entry...

    UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, _username)
    

    ...so I know it's not my LDAP server's SSL setup, since it works over SSL for other lookups.

    Has anyone gotten the ValidateCredentials(...) call to work over SSL? Can you explain how? Or is there another/better way to securely validate AD/LDAP credentials?

    • CAbbott
      CAbbott almost 12 years
      Here's an MSDN article for troubleshooting LDAP over SSL: support.microsoft.com/kb/938703
    • Nate Sauber
      Nate Sauber almost 12 years
      Thanks for the link. But again I can communicate over LDAPS (port 636) just fine for all the other LDAP queries I've performed. It seems like something unusual about ValidateCredentials(). I'll look through the article in more detail, though.
    • Terry Gardner
      Terry Gardner almost 12 years
      Passwords should be transmitted in clear text - not hashed - over a secure connection to a server that supports password quality checks and password history enforcement unless the LDAP client provides password quality and history checks, otherwise, the server will not be able to enforce the quality and history.
  • Tolga Evcimen
    Tolga Evcimen over 9 years
    I see you say " Or whatever your server need to perform authentication.". How can I know what a server needs for authentication? I am working on a server which is previously set with some options, but I don't know what are they now. Can I check them somehow?
  • Mike Bouck
    Mike Bouck about 5 years
    It appears you were trying to talk to AD LDS (i.e. Lightweight Directory Services) and not AD DS (regular Active Directory). If that was the case then your original code didn't work because you specified the wrong ContextType. Per Microsoft docs if you want to query AD LDS then you need to specify ContextType.ApplicationDirectory. ContextType.Domain is for regular Active Directory only. docs.microsoft.com/en-us/dotnet/api/…