588

How can I validate a username and password against Active Directory? I simply want to check if a username and password are correct.

Marc
  • 16,170
  • 20
  • 76
  • 119

15 Answers15

702

If you work on .NET 3.5 or newer, you can use the System.DirectoryServices.AccountManagement namespace and easily verify your credentials:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

It's simple, it's reliable, it's 100% C# managed code on your end - what more can you ask for? :-)

Read all about it here:

Update:

As outlined in this other SO question (and its answers), there is an issue with this call possibly returning True for old passwords of a user. Just be aware of this behavior and don't be too surprised if this happens :-) (thanks to @MikeGledhill for pointing this out!)

Community
  • 1
  • 1
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 1
    I tried: PrincipalContext pc = new PrincipalContext(ContextType.Domain, "yourdomain"); Your mileage may vary. – Christian Payne May 29 '09 at 05:03
  • Christian: you're absolutely right. I didn't have a server at hand to test my assumptions - and they were wrong. You need the plain domain name - not the LDAP domain descriptor. Thanks for pointing that out! – marc_s May 29 '09 at 06:38
  • 1
    This worked great, only for me I had to prefix the username with the domain name, ie pc.ValidateCredentials("DOMAN\\UserName", "Password"); – Michael La Voie Oct 29 '09 at 16:23
  • @myotherme: yes, get a UserPrincipal, and then use the `user.GetAuthorizationGroups();` to get that list. Check out the great MSDN article on that topic: http://msdn.microsoft.com/en-us/magazine/cc135979.aspx – marc_s Apr 12 '10 at 12:45
  • 42
    In my domain, I had to specify pc.ValidateCredentials("myuser", "mypassword", ContextOptions.Negotiate) or I would get System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests. – Alex Peck Jun 29 '11 at 14:14
  • 12
    If a password's expired or the accounts disabled, then ValidateCredentials will return false. Unfortuantly, it doesn't tell you *why* it's returned false (which is a pity as it means I can't do something sensible like redirect the user to change their password). – Chris J Sep 08 '11 at 15:10
  • 71
    Also beware the 'Guest' account -- if the domain-level Guest account is enabled, ValidateCredentials returns true if you give it a *non-existant* user. As a result, you may want to call `UserPrinciple.FindByIdentity` to see if the passed in user ID exists first. – Chris J Sep 08 '11 at 15:17
  • 1
    We did in our dev/test domain (one that gets buggered about with randomly to test different scenarios). If it wasn't for that, it wouldn't have been caught, leaving a gaping security hole. The problem is whilst we have control over our own domain, we don't have any control over our customer's domain, so I'd rather have bullet proof code than make assumptions :-) Pity this behaviour wasn't documented on the MSDN as it took an age to track down... – Chris J Sep 09 '11 at 08:48
  • 8
    @AlexPeck: the reason why you had to do this (like me) was that .NET uses the following technologies by default: LDAP+SSL, Kerberos, then RPC. I suspect RPC is off in your network (good!) and Kerberos doesn't actually get used by .NET unless you explicitly tell it using `ContextOptions.Negotiate`. – Brett Veenstra Sep 20 '11 at 22:37
  • 2
    I also want to add that I needed to set `ContextOptions.Negotiate` to fix a performance issue on our production server. It was taking 20-30 seconds to validate credentials without specifying that enumeration. – Justin Helgerson Apr 01 '12 at 21:01
  • 1
    @Sarah - it's pretty safe to assume that the password would be passed into a method containing this code by some process (user input etc) - the code shown is just an example but you need to, at some point, pass a password **somewhere**! – Charleh Sep 01 '12 at 12:43
  • @Kisame: use `UserPrincipal.Current` – marc_s Dec 17 '12 at 10:05
  • 2
    Beware - if you use this to validate and you have a AD lockout policy (e,g, three incorrect attempts), it will lock you out. – rbrayb Jun 06 '13 at 19:07
  • I get error as: A local error occurred. Stack trace: at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential) at System.DirectoryServices.AccountManagement.CredentialValidator.lockedLdapBind(LdapConnection current, NetworkCredential creds, ContextOptions contextOptions) at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions) at System.DirectoryServices.AccountManagement.Crede – variable Aug 20 '14 at 07:13
  • 1
    @marc_s, is there a way to handle exceptions using this code such as `incorrect username/pwd`, 'account disabled`, etc...? Thanks – smr5 Jan 20 '15 at 18:25
  • @Sam: well, if the return value is `false` - then something is wrong (credentials not valid). I don't think you can get any more information than that... (also you shouldn't be showing any more to a user anyway!) – marc_s Jan 20 '15 at 18:29
  • 2
    If you get that DirectoryOperationException, instead of dropping straight down to just Negotiate as mentioned in comments above, at least try Negotiate | Signing | Sealing first. You might think that's what it did by default without any options (based on documentation in the PrincipalContext constructuor), but if you use ILSpy you'll see the default it tries is actually Simple | SecureSocketLayer. And with this type of exception it just fails and doesn't try anything else. Try with Negotiate | Signing | Sealing and if that works you're still secure. That's what happened/worked for me. – eol Mar 19 '15 at 19:56
  • 5
    Do be aware that if the user CHANGES their Active Directory password, this piece of code will continue to happily authenticate the user using their old AD password. Yup, really. Have a read here: http://stackoverflow.com/questions/8949501/why-does-active-directory-validate-last-password – Mike Gledhill Mar 30 '15 at 09:30
  • 1
    How can I get error codes like **http://stackoverflow.com/a/11033489/206730** using `PrincipalContext.ValidateCredentials` ? – Kiquenet Feb 17 '16 at 11:26
  • @Kiquenet: you cannot - `ValidateUser` will only ever return a **bool** (true/false) - nothing more – marc_s Feb 17 '16 at 12:40
  • Not valid for `secure LDAP connection (aka LDAPS, which uses port 636)` ? – Kiquenet Dec 19 '16 at 15:46
  • @BrettVeenstra Why reason using *ContextOptions.Negotiate*? I get `The server cannot handle directory requests` error using my *TestAd.aspx*. In the same server: In a website get the error. In another website it works right. Validate **the same credentials** both. – Kiquenet Dec 23 '16 at 17:21
  • @nzpcmad You should set your AD timeout policy to temporary lockout. This is good security practice to prevent brute force account compromise. – Shiv Jan 10 '17 at 03:48
  • 1
    @ChrisJ This is good security practice as you do not want to give information if someone was guessing accounts. If they get the username, password wrong, don't tell them which one, nor if the password is expired. Only honour legit login credentials. This protects you from account name harvesting for example which can be used to attempt compromise via other vectors. – Shiv Jan 10 '17 at 03:50
  • @ChrisJ well you could quite easily validate the username existence and do checks on enabled status yourself. That would then imply if the password was the issue. It makes sense for this method to not reveal the username/pw info as extremely often developers just pass on the error message. This encourages best practices by being the default. – Shiv Jan 12 '17 at 06:11
76

We do this on our Intranet

You have to use System.DirectoryServices;

Here are the guts of the code

using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword))
{
    using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry))
    {
        //adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
        adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";

        try
        {
            SearchResult adsSearchResult = adsSearcher.FindOne();
            bSucceeded = true;

            strAuthenticatedBy = "Active Directory";
            strError = "User has been authenticated by Active Directory.";
        }
        catch (Exception ex)
        {
            // Failed to authenticate. Most likely it is caused by unknown user
            // id or bad strPassword.
            strError = ex.Message;
        }
        finally
        {
            adsEntry.Close();
        }
    }
}
Community
  • 1
  • 1
DiningPhilanderer
  • 2,737
  • 3
  • 25
  • 29
  • 12
    What do you put in "path"? The name of the domain? The name of the server? The LDAP path to the domain? The LDAP path to the server? – Ian Boyd Dec 01 '08 at 15:00
  • 4
    Answer1: No we run it as a web service so it can be called from multiple locations in the main web app. Answer2: Path contains LDAP info... LDAP://DC=domainname1,DC=domainname2,DC=com – DiningPhilanderer Dec 01 '08 at 18:21
  • 5
    It would seem that this could allow LDAP injection. You may want to make sure to escape or remove any parenthesis in the strAccountId – Brain2000 Oct 09 '14 at 15:16
  • Does this mean that `strPassword` is stored in LDAP in plain text? – Matt Kocaj Jul 17 '15 at 09:28
  • 20
    There should never be a need to explicitly call `Close()` on a `using` variable. – Nyerguds May 17 '16 at 09:28
  • I need validate and then get some information of validated user. this code work for me. tank you – Mazaher Bazari Jun 03 '19 at 07:48
  • use a path like entry.Path = @"LDAP://OU=""Company Users"",OU=Company,DC=boise,DC=esiconstruction,DC=com"; The path is right to left where the rightmost ou organization unit is the top node. – Golden Lion Jun 18 '20 at 16:28
  • How to authenticate the DirectoryEntry with Managed Service account(Password is not available)? – Vara Prasad.M Aug 31 '21 at 14:23
75

Several solutions presented here lack the ability to differentiate between a wrong user / password, and a password that needs to be changed. That can be done in the following way:

using System;
using System.DirectoryServices.Protocols;
using System.Net;

namespace ProtocolTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                LdapConnection connection = new LdapConnection("ldap.fabrikam.com");
                NetworkCredential credential = new NetworkCredential("user", "password");
                connection.Credential = credential;
                connection.Bind();
                Console.WriteLine("logged in");
            }
            catch (LdapException lexc)
            {
                String error = lexc.ServerErrorMessage;
                Console.WriteLine(lexc);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        }
    }
}

If the users password is wrong, or the user doesn't exists, error will contain

"8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1",

if the users password needs to be changed, it will contain

"8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 773, v1db1"

The lexc.ServerErrorMessage data value is a hex representation of the Win32 Error Code. These are the same error codes which would be returned by otherwise invoking the Win32 LogonUser API call. The list below summarizes a range of common values with hex and decimal values:

525​ user not found ​(1317)
52e​ invalid credentials ​(1326)
530​ not permitted to logon at this time​ (1328)
531​ not permitted to logon at this workstation​ (1329)
532​ password expired ​(1330)
533​ account disabled ​(1331) 
701​ account expired ​(1793)
773​ user must reset password (1907)
775​ user account locked (1909)
codechurn
  • 3,870
  • 4
  • 45
  • 65
Søren Mors
  • 988
  • 6
  • 6
  • 2
    Unfortunately, some AD installations doesn't return the LDAP sub error code, which means that this solution won't work. – Søren Mors Jun 18 '12 at 09:33
  • 4
    Don't forget to add some references to the project: `System.DirectoryServices` and `System.DirectoryServices.Protocols` – TomXP411 Apr 04 '13 at 22:46
  • 3
    My question, though, is this: how do you get the LDAP server name? If you're writing a portable application, you can't expect the user to know or need to supply the names of AD servers on every network. – TomXP411 Apr 04 '13 at 22:52
  • 1
    I have users which are restricted to logging in to specific workstations; how do I specify the workstation that I am trying the login for? (workstation1 would fail with data 531, workstation2 would work fine, for example) – akohlsmith Jun 03 '14 at 21:10
  • 3
    I feel weird as I don't think you're getting enough credit. This is a fully managed method without the trouble of Win32 API call to determine if "user must reset password" which clearly none of the other answers achieved. Is there any loophole in this method causing it's low appreciation rate? hmm... – Lionet Chen Jan 08 '18 at 07:36
  • It should be noted that this will not differentiate between an invalid password and a user is locked out, because a locked account will return a 775 even if the password is wrong. – saluce Aug 10 '21 at 21:36
37

very simple solution using DirectoryServices:

using System.DirectoryServices;

//srvr = ldap server, e.g. LDAP://domain.com
//usr = user name
//pwd = user password
public bool IsAuthenticated(string srvr, string usr, string pwd)
{
    bool authenticated = false;

    try
    {
        DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd);
        object nativeObject = entry.NativeObject;
        authenticated = true;
    }
    catch (DirectoryServicesCOMException cex)
    {
        //not authenticated; reason why is in cex
    }
    catch (Exception ex)
    {
        //not authenticated due to some other exception [this is optional]
    }

    return authenticated;
}

the NativeObject access is required to detect a bad user/password

Markus Safar
  • 6,324
  • 5
  • 28
  • 44
Steven A. Lowe
  • 60,273
  • 18
  • 132
  • 202
  • 4
    This code is bad because it's also doing an authorization check (check if the user is allowed to read active directory information). The username and password can be valid, but the user not allowed to read info - and get an exception. In other words you can have a valid username&password, but still get an exception. – Ian Boyd Aug 18 '11 at 13:49
  • 2
    i am actually in the process of asking for the *native* equivalent of `PrincipleContext` - which only exists in .NET 3.5. But if you are using .NET 3.5 or newer you should use `PrincipleContext` – Ian Boyd Aug 18 '11 at 16:57
28

Unfortunately there is no "simple" way to check a users credentials on AD.

With every method presented so far, you may get a false-negative: A user's creds will be valid, however AD will return false under certain circumstances:

  • User is required to Change Password at Next Logon.
  • User's password has expired.

ActiveDirectory will not allow you to use LDAP to determine if a password is invalid due to the fact that a user must change password or if their password has expired.

To determine password change or password expired, you may call Win32:LogonUser(), and check the windows error code for the following 2 constants:

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330
Alan
  • 45,915
  • 17
  • 113
  • 134
  • 1
    May I ask where you got the devinitions for Expired and Must_Change... Found them nowhere but here :) – mabstrei Nov 19 '12 at 10:13
  • 1
    From an MSDN article: http://msdn.microsoft.com/en-us/library/windows/desktop/ms681386%28v=vs.85%29.aspx – Alan Nov 19 '12 at 16:38
  • Thanks. I was trying to find out way my validation was returning false all the time. It was because the user needs to change his password. – Deise Vicentin Nov 27 '17 at 13:35
22

Probably easiest way is to PInvoke LogonUser Win32 API.e.g.

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

MSDN Reference here...

http://msdn.microsoft.com/en-us/library/aa378184.aspx

Definitely want to use logon type

LOGON32_LOGON_NETWORK (3)

This creates a lightweight token only - perfect for AuthN checks. (other types can be used to build interactive sessions etc.)

stephbu
  • 5,072
  • 26
  • 42
  • As @Alan points out, LogonUser API has many useful traits beyond a System.DirectoryServices call. – stephbu Dec 01 '08 at 17:48
  • 4
    @cciotti: No, that's wrong. The BEST way to correctly authenticate someone is to use LogonUserAPI as @stephbu write. All other methods described in this post will NOT WORK 100%. Just a note however, I do believe you have to be domain joined inorder to call LogonUser. – Alan Apr 20 '09 at 18:28
  • @Alan to generate credentials you have to be able to connect to the domain by handing in a valid domain account. However I'm pretty sure your machine doesn't necessarily need to be a member of the domain. – stephbu Apr 21 '09 at 02:15
  • 2
    The `LogonUser` API requires the user to have the **Act as a part of the operating system** privelage; which isn't something that users get - and not something you want to be granting to every user in the organization. (http://msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx) – Ian Boyd Aug 18 '11 at 13:52
  • Agreed Ian, only LogonUser and SSPI can express the full range of possible results from cred checks. Doing SSPI in C# as asked for by the OP is far from simple hence the LogonUser recommendation. You seen a library that bundles it up elegantly without resorting to unmanaged code. I've not looked TBH. – stephbu Aug 24 '11 at 06:20
  • 1
    LogonUser only needs *Act as part of the operating system* for Windows 2000 and below according to http://support.microsoft.com/kb/180548 ... It looks clean for Server 2003 and higher. – Chris J Sep 08 '11 at 15:14
  • LogonUser will not work if the domain is not a domain you trust. If you want to validate domain credentials, you will have to bind to the AD's LDAP server. – Ian Boyd Mar 19 '15 at 14:14
19

A full .Net solution is to use the classes from the System.DirectoryServices namespace. They allow to query an AD server directly. Here is a small sample that would do this:

using (DirectoryEntry entry = new DirectoryEntry())
{
    entry.Username = "here goes the username you want to validate";
    entry.Password = "here goes the password";

    DirectorySearcher searcher = new DirectorySearcher(entry);

    searcher.Filter = "(objectclass=user)";

    try
    {
        searcher.FindOne();
    }
    catch (COMException ex)
    {
        if (ex.ErrorCode == -2147023570)
        {
            // Login or password is incorrect
        }
    }
}

// FindOne() didn't throw, the credentials are correct

This code directly connects to the AD server, using the credentials provided. If the credentials are invalid, searcher.FindOne() will throw an exception. The ErrorCode is the one corresponding to the "invalid username/password" COM error.

You don't need to run the code as an AD user. In fact, I succesfully use it to query informations on an AD server, from a client outside the domain !

surfmuggle
  • 5,527
  • 7
  • 48
  • 77
Mathieu Garstecki
  • 1,589
  • 1
  • 13
  • 18
  • how about authentication types? i think you forgot it in your code above. :-) by default DirectoryEntry.AuthenticationType is set to Secured right? that code is not going to work on LDAPs that are not secured (Anonymous or None perhaps). am i correct with this? – jerbersoft Nov 12 '10 at 03:47
  • The down-side to *querying* an AD server is that you have **permission** to query the AD server. Your credential can be valid, but if you don't have permission to query AD, then you will get the error. That is why the so-called *Fast Bind* was created; you validate credentials without authorizing the user's ability to do something. – Ian Boyd Mar 19 '15 at 14:16
  • 2
    wouldn't this allow anyone to pass in case a COMException is thrown for any other reason before the credentials are checked? – Stefan Paul Noack Jan 05 '17 at 17:09
14

Yet another .NET call to quickly authenticate LDAP credentials:

using System.DirectoryServices;

using(var DE = new DirectoryEntry(path, username, password)
{
    try
    {
        DE.RefreshCache(); // This will force credentials validation
    }
    catch (COMException ex)
    {
        // Validation failed - handle how you want
    }
}
palswim
  • 11,856
  • 6
  • 53
  • 77
11

Try this code (NOTE: Reported to not work on windows server 2000)

#region NTLogonUser
#region Direct OS LogonUser Code
[DllImport( "advapi32.dll")]
private static extern bool LogonUser(String lpszUsername, 
    String lpszDomain, String lpszPassword, int dwLogonType, 
    int dwLogonProvider, out int phToken);

[DllImport("Kernel32.dll")]
private static extern int GetLastError();

public static bool LogOnXP(String sDomain, String sUser, String sPassword)
{
   int token1, ret;
   int attmpts = 0;

   bool LoggedOn = false;

   while (!LoggedOn && attmpts < 2)
   {
      LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1);
      if (LoggedOn) return (true);
      else
      {
         switch (ret = GetLastError())
         {
            case (126): ; 
               if (attmpts++ > 2)
                  throw new LogonException(
                      "Specified module could not be found. error code: " + 
                      ret.ToString());
               break;

            case (1314): 
               throw new LogonException(
                  "Specified module could not be found. error code: " + 
                      ret.ToString());

            case (1326): 
               // edited out based on comment
               //  throw new LogonException(
               //   "Unknown user name or bad password.");
            return false;

            default: 
               throw new LogonException(
                  "Unexpected Logon Failure. Contact Administrator");
              }
          }
       }
   return(false);
}
#endregion Direct Logon Code
#endregion NTLogonUser

except you'll need to create your own custom exception for "LogonException"

Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • Don't use exception handling for returning information from a method. "Unknown user name or bad password" is not exceptional, it is standard behaviour for LogonUser. Just return false. – Treb Nov 14 '08 at 16:20
  • yes... this was a port from an old VB6 library... written 2003 or so... (when .Net first came out) – Charles Bretana Nov 17 '08 at 15:18
  • If running on Windows 2000 this code will not work (http://support.microsoft.com/kb/180548) – Ian Boyd Dec 01 '08 at 14:58
  • 2
    Rethinking this. Logon User's expected behavior, it's purpose, is to *log the user on*. If it fails to perform that task, it **IS** an exception. In fact, the method should return void, not a Boolean. Plus, if you just returned a Boolean the method's consumer has no way to inform the user what the reason for the failure was. – Charles Bretana May 24 '18 at 12:34
8

Windows authentication can fail for various reasons: an incorrect user name or password, a locked account, an expired password, and more. To distinguish between these errors, call the LogonUser API function via P/Invoke and check the error code if the function returns false:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

public static class Win32Authentication
{
    private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() // called by P/Invoke
            : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(this.handle);
        }
    }

    private enum LogonType : uint
    {
        Network = 3, // LOGON32_LOGON_NETWORK
    }

    private enum LogonProvider : uint
    {
        WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUser(
        string userName, string domain, string password,
        LogonType logonType, LogonProvider logonProvider,
        out SafeTokenHandle token);

    public static void AuthenticateUser(string userName, string password)
    {
        string domain = null;
        string[] parts = userName.Split('\\');
        if (parts.Length == 2)
        {
            domain = parts[0];
            userName = parts[1];
        }

        SafeTokenHandle token;
        if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
            token.Dispose();
        else
            throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
    }
}

Sample usage:

try
{
    Win32Authentication.AuthenticateUser("EXAMPLE\\user", "P@ssw0rd");
    // Or: Win32Authentication.AuthenticateUser("user@example.com", "P@ssw0rd");
}
catch (Win32Exception ex)
{
    switch (ex.NativeErrorCode)
    {
        case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
            // ...
        case 1327: // ERROR_ACCOUNT_RESTRICTION
            // ...
        case 1330: // ERROR_PASSWORD_EXPIRED
            // ...
        case 1331: // ERROR_ACCOUNT_DISABLED
            // ...
        case 1907: // ERROR_PASSWORD_MUST_CHANGE
            // ...
        case 1909: // ERROR_ACCOUNT_LOCKED_OUT
            // ...
        default: // Other
            break;
    }
}

Note: LogonUser requires a trust relationship with the domain you're validating against.

Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • can you explain why your answer is better than the highest voted answer? – Mohammad Ali Mar 17 '20 at 08:28
  • 1
    @MohammadAli: If you need to know why credential validation failed (incorrect credentials, a locked account, an expired password, etc.), the LogonUser API function will tell you. In contrast, the PrincipalContext.ValidateCredentials method (according to comments on marc_s's answer) won't; it returns false in all these cases. On the other hand, LogonUser requires a trust relationship with the domain, but PrincipalContext.ValidateCredentials (I think) does not. – Michael Liu Mar 17 '20 at 14:17
5

If you are stuck with .NET 2.0 and managed code, here is another way that works whith local and domain accounts:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;

static public bool Validate(string domain, string username, string password)
{
    try
    {
        Process proc = new Process();
        proc.StartInfo = new ProcessStartInfo()
        {
            FileName = "no_matter.xyz",
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            UseShellExecute = false,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            RedirectStandardInput = true,
            LoadUserProfile = true,
            Domain = String.IsNullOrEmpty(domain) ? "" : domain,
            UserName = username,
            Password = Credentials.ToSecureString(password)
        };
        proc.Start();
        proc.WaitForExit();
    }
    catch (System.ComponentModel.Win32Exception ex)
    {
        switch (ex.NativeErrorCode)
        {
            case 1326: return false;
            case 2: return true;
            default: throw ex;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }

    return false;
}   
chauwel
  • 59
  • 1
  • 1
  • Works well with local accounts of the machine he launch the script – eka808 Nov 29 '11 at 10:15
  • BTW, this method is needed to make this works public static SecureString ToSecureString(string PwString) { char[] PasswordChars = PwString.ToCharArray(); SecureString Password = new SecureString(); foreach (char c in PasswordChars) Password.AppendChar(c); ProcessStartInfo foo = new ProcessStartInfo(); foo.Password = Password; return foo.Password; } – eka808 Nov 29 '11 at 10:19
  • On the contrary, one should use SecureString for passwords anyway. WPF PasswordBox supports it. – Stephen Drew Apr 09 '12 at 21:07
3

My Simple Function

 private bool IsValidActiveDirectoryUser(string activeDirectoryServerDomain, string username, string password)
    {
        try
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://" + activeDirectoryServerDomain, username + "@" + activeDirectoryServerDomain, password, AuthenticationTypes.Secure);
            DirectorySearcher ds = new DirectorySearcher(de);
            ds.FindOne();
            return true;
        }
        catch //(Exception ex)
        {
            return false;
        }
    }
hossein andarkhora
  • 740
  • 10
  • 23
1

For me both of these below worked, make sure your Domain is given with LDAP:// in start

//"LDAP://" + domainName
private void btnValidate_Click(object sender, RoutedEventArgs e)
{
    try
    {
        DirectoryEntry de = new DirectoryEntry(txtDomainName.Text, txtUsername.Text, txtPassword.Text);
        DirectorySearcher dsearch = new DirectorySearcher(de);
        SearchResult results = null;

        results = dsearch.FindOne();

        MessageBox.Show("Validation Success.");
    }
    catch (LdapException ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
}

private void btnValidate2_Click(object sender, RoutedEventArgs e)
{
    try
    {
        LdapConnection lcon = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
        NetworkCredential nc = new NetworkCredential(txtUsername.Text,
                               txtPassword.Text, txtDomainName.Text);
        lcon.Credential = nc;
        lcon.AuthType = AuthType.Negotiate;
        lcon.Bind(nc);

        MessageBox.Show("Validation Success.");
    }
    catch (LdapException ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
}
dnxit
  • 7,118
  • 2
  • 30
  • 34
0

Here my complete authentication solution for your reference.

First, add the following four references

 using System.DirectoryServices;
 using System.DirectoryServices.Protocols;
 using System.DirectoryServices.AccountManagement;
 using System.Net; 

private void AuthUser() { 


      try{
            string Uid = "USER_NAME";
            string Pass = "PASSWORD";
            if (Uid == "")
            {
                MessageBox.Show("Username cannot be null");
            }
            else if (Pass == "")
            {
                MessageBox.Show("Password cannot be null");
            }
            else
            {
                LdapConnection connection = new LdapConnection("YOUR DOMAIN");
                NetworkCredential credential = new NetworkCredential(Uid, Pass);
                connection.Credential = credential;
                connection.Bind();

                // after authenticate Loading user details to data table
                PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
                UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
                DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
                DirectorySearcher deSearch = new DirectorySearcher(up_User);
                SearchResultCollection results = deSearch.FindAll();
                ResultPropertyCollection rpc = results[0].Properties;
                DataTable dt = new DataTable();
                DataRow toInsert = dt.NewRow();
                dt.Rows.InsertAt(toInsert, 0);

                foreach (string rp in rpc.PropertyNames)
                {
                    if (rpc[rp][0].ToString() != "System.Byte[]")
                    {
                        dt.Columns.Add(rp.ToString(), typeof(System.String));

                        foreach (DataRow row in dt.Rows)
                        {
                            row[rp.ToString()] = rpc[rp][0].ToString();
                        }

                    }  
                }
             //You can load data to grid view and see for reference only
                 dataGridView1.DataSource = dt;


            }
        } //Error Handling part
        catch (LdapException lexc)
        {
            String error = lexc.ServerErrorMessage;
            string pp = error.Substring(76, 4);
            string ppp = pp.Trim();

            if ("52e" == ppp)
            {
                MessageBox.Show("Invalid Username or password, contact ADA Team");
            }
            if ("775​" == ppp)
            {
                MessageBox.Show("User account locked, contact ADA Team");
            }
            if ("525​" == ppp)
            {
                MessageBox.Show("User not found, contact ADA Team");
            }
            if ("530" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
            }
            if ("531" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
            }
            if ("532" == ppp)
            {
                MessageBox.Show("Password expired, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }



        } //common error handling
        catch (Exception exc)
        {
            MessageBox.Show("Invalid Username or password, contact ADA Team");

        }

        finally {
            tbUID.Text = "";
            tbPass.Text = "";

        }
    }
0

I'm using this procedure as a DLL to login in other app that we developed...
(We are currently using this with OpenEdge Progress)

public static string AzureLogin(string user, string password) {

    string status;

    try {
        new DirectorySearcher(new DirectoryEntry("LDAP://yourdomain.com", user, password) {
            AuthenticationType = AuthenticationTypes.Secure,
            Username = user,
            Password = password
        })  {
            Filter = "(objectclass=user)"
        }.FindOne().Properties["displayname"][0].ToString();

        status = $"SUCCESS - User {user} has logged in.";

    } catch(System.Exception e) {
        status = $"ERROR - While logging in: {e}";
    }

    return status;
}
Fábio Nascimento
  • 2,644
  • 1
  • 21
  • 27
Raphael Frei
  • 361
  • 1
  • 10