18

I have an MVC application that needs to login and verify a user against Active Directory. I am using the PrincipalContext.ValidateCredentials method but always get a authentication of false.

Connecting to the Server is fine. The problem seems to occur in the ValidateCredentials.

Here is my code:

public static bool IsAuthenticated(string domain, string username, string pwd) {
    bool IsAuthenticated = false;

    try {
        PrincipalContext insPrincipalContext = 
            new PrincipalContext(ContextType.Domain, domain, "DC=c1w,DC=com");

        username = "c1w\\" + username;

        IsAuthenticated = insPrincipalContext.ValidateCredentials(username, pwd);
    }
    catch (Exception ex)
    {
        // Rethrow this exception
        ExceptionPolicy.HandleException(ex, "Exception Policy");
    }

    return IsAuthenticated;
}

Anyone know why this would be happening?

rae1
  • 6,066
  • 4
  • 27
  • 48
Billy Logan
  • 2,833
  • 16
  • 43
  • 49

3 Answers3

20

Here's how ValidateCredentials(string, string) works: First, it tries to authenticate with the Negotiate, Signing, and Sealing context options. If this fails, it tries again with SimpleBind and SecureSocketLayer.

The problem is that the NT4 (AKA "legacy", AKA "down-level name") format (DOMAIN\UserName, or more correctly, NetBiosName\SamAccountName) doesn't work with Negotiate. But it does work with SimpleBind.

So what's probably happening when calling the 2-parameter ValidateCredentials() method, is that it first fails using Negotiate because it doesn't like the NT4 format, and then fails again when using simple bind.

During my own testing, I've found that the reason why it fails even after falling back to using simple bind is that it's not only using SimpleBind. It's using SimpleBind plus SecureSocketLayer. This means that it will still fail if the Active Directory server isn't set up correctly to use SSL (a common scenario for test environments).

As was mentioned in one of the comments, you NEVER, NEVER want to use SimpleBind by itself (without SecureSocketLayer), otherwise your passwords are sent over the network in plain text.

In the wild, I've seen that some Active Directory systems don't allow the use of simple binds at all, so you must make it work with Negotiate.

I've found 2 ways to deal with this problem:

1) If everything is happening on the same domain, you should be able to call ValidateCredentials with only the username (SAM account name), leaving out the "DOMAIN\" part. Then, it will work properly the first time with Negotiate.

2) If the domain part is important because there may be multiple domains involved (i.e. Domain1\UserA and Domain2\UserA are different people), then it gets a bit more complicated. In this case what I ended up doing was translating the NT4 name (DOMAIN\User) to "user principal name" format (e.g. LogonName@domain.com). There are a couple different ways to do this. The easiest is probably to use the 3-parameter overload of UserPrincipal.FindByIdentity(), and then grab the value of the UserPrincipalName property on the result. Another way would be to use a DirectorySearcher and query LDAP://domain for the userPrincipalName property of the user with the matching sAMAccountName value. Note: this solution will only work if all the domains involved are in the same forest.

RobSiklos
  • 8,348
  • 5
  • 47
  • 77
  • According to the answer `Domain\UserName` doesn't work with Negotiate. But how come that when providing `ContextOptions` explicitly in the method call like `context.ValidateCredentials(userName, password, ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing)` it works? It's confusing. – managerger Apr 28 '22 at 08:05
9

I don't see where you initializes the "pwd" variable Maybe you should use ContextOption in this method to specify exactly the reqired behaviour. Sorry for too broad response but there is no much details in your question

fedotoves
  • 757
  • 6
  • 7
  • Editted the question to include the entire method. Will try the ContextOption suggestion. Thank you. – Billy Logan Mar 02 '10 at 14:27
  • 5
    B-Rain, The ContextOption reference pointed me in the right direction. Ended up using the ContextOptions.Negotiate on my call to AD and ContextOptions.SimpleBind on the validate credentials. Simple Bind will work for me since the site will be SSL Secured. Thanks for your help. – Billy Logan Mar 02 '10 at 15:30
  • Upvoted both the question and the answer because this helped me in my situation as well. In my case, my dev machine (where login works without context specified) is in the secured zone on the network, but the web server (where login doesn't work without context specified) is in the DMZ. I used the same configuration as @Billy Logan - Negotiate on the call to AD and SimpleBind on the validate call. – Matt Mills Oct 14 '10 at 19:52
  • So what did you use? ContextOptions.Negotiate or ContextOptions.SimpleBind? – Tien Do May 25 '12 at 09:18
  • 2
    If you use ContextOptions.Simplebind, be aware that the password may be sent in clear text over the wire. – ms007 Oct 23 '12 at 09:33
1

It seems you are validating the user with domain\userName format. You might want to parse the domain name from userName and user the ValidateCredential.

Purvi
  • 11
  • 3