Validate a username and password against Active Directory? - c#

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

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:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
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!)

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();
}
}
}

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)

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

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

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.)

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 !

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
}
}

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"

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.

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;
}

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;
}
}

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}");
}
}

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 = "";
}
}

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;
}

Related

How to authenticate in LDAP in C#?

I am new to LDAP related coding and today I am asked to develop a code to check the users authentication against LDAP.
The tutorials I have found online are so simple but our company's Directory is so complicated that I don't know how to write a code for that. Here is the info of the LDAP . I have changed the company name to hide the name.
string domain = "ou=People,dc=my,dc=com";
string LDAP_Path= "dc01.my.com;
string LDAPconnect= "LDAP://dc01.my.com/";
Here is a code I have developed but it gives me error when run " LdapResult = LdapSearcher.FindOne();":
string domain = "ou=People,dc=my,dc=com";
string password = "";
string userName = "";
// define your connection
LdapConnection ldapConnection = new LdapConnection(LDAP_Path);
try
{
// authenticate the username and password
using (ldapConnection)
{
// pass in the network creds, and the domain.
var networkCredential = new NetworkCredential(userName, password, domain);
// if we're using unsecured port 389, set to false. If using port 636, set this to true.
ldapConnection.SessionOptions.SecureSocketLayer = false;
// since this is an internal application, just accept the certificate either way
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
// to force NTLM\Kerberos use AuthType.Negotiate, for non-TLS and unsecured, just use AuthType.Basic
ldapConnection.AuthType = AuthType.Basic;
// authenticate the user
ldapConnection.Bind(networkCredential);
Response.Write( "connect ldap success");
}
}
catch (LdapException ldapException)
{
Response.Write(ldapException + " <p>Ad connect failed</p>");
//Authentication failed, exception will dictate why
}
string strTmp0 = LDAPconnect + domain;
string user = "memberId";
string pwd = "memberPwd";
System.DirectoryServices.DirectoryEntry LdapEntry = new System.DirectoryServices.DirectoryEntry(strTmp0, "cn=" + user, pwd, AuthenticationTypes.None);
DirectorySearcher LdapSearcher = new DirectorySearcher(LdapEntry);
LdapSearcher.Filter = "(cn=" + user + ")";
string value = string.Empty;
SearchResult LdapResult=null;
try
{
LdapResult = LdapSearcher.FindOne();
}
catch (Exception ex)
{
Response.Write(ex.Message.ToString());
// .............get Error msg : username an password uncorrect
}
if ((LdapResult != null))
{
Response.Write("ldapresult not null");
}
Could anybody help plz?
In ldap connection setting , OP should use own configuration.
// Ldap connection setting. this should setup according to organization ldap configuration
int portnumber = 12345;
LdapConnection ldapConnection = new LdapConnection(new LdapDirectoryIdentifier("ldap.testxxxx.com", portnumber));
ldapConnection.AuthType = AuthType.Anonymous;
ldapConnection.Bind();
SearchRequest Srchrequest = null;
SearchResponse SrchResponse = null;
SearchResultEntryCollection SearchCollection = null;
Hashtable UserDetails = new Hashtable();
Srchrequest = new SearchRequest("distniguishged name e.g. o=testxxx.com", string.Format(CultureInfo.InvariantCulture, "preferredmail=test#testxxxx.com"), System.DirectoryServices.Protocols.SearchScope.Subtree);
SrchResponse = (SearchResponse)ldapConnection.SendRequest(Srchrequest);
SearchCollection = SrchResponse.Entries;
foreach (SearchResultEntry entry in SearchCollection)
{
foreach (DictionaryEntry att in entry.Attributes)
{
if (((DirectoryAttribute)(att.Value)).Count > 0)
{
UserDetails.Add(att.Key.ToString(), ((DirectoryAttribute)(att.Value))[0].ToString());
}
else
{
UserDetails.Add(att.Key.ToString(), string.Empty);
}
}
}
if (UserDetails.Count > 1)
{
Console.WriteLine("User exists");
}
else
{
Console.WriteLine("User does not exist");
}
You can use the DirectoryInfo conrstructor that has user and password arguments. That way, you don't need to do a query to the LDAP, you can simplify your code.
string username = "frederic";
string password = "myFanciPassword99";
string domain = "ou=People,dc=my,dc=com";
string LDAPconnect= "LDAP://dc01.my.com/";
string connectionString = LDAPconnect + domain;
bool userValid = false;
// Note: DirectoryEntry(domain, username, password) would also work
DirectoryEntry entry = new DirectoryEntry(connectionString, username, password);
try
{
// Bind to the native AdsObject to force authentication.
Object obj = entry.NativeObject;
userValid = true;
}
catch (Exception ex)
{
}

Validate user credentials against windows login [duplicate]

How can I validate a username and password against Active Directory? I simply want to check if a username and password are correct.
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:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
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!)
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();
}
}
}
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)
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
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
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.)
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 !
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
}
}
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"
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.
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;
}
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;
}
}
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}");
}
}
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 = "";
}
}
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;
}

C# Access a local folder with admin account credentials

I have two user accounts in my computer
User 1 (regular user)
Admin
Now I have folder "e:\Folder1" for which only Admin account has full access. User ! cannot access the folder.
I have a WPF Application Where It would run only within the User 1 account. I want the user 1 to user the wpf application to access "Folder1".
I want to know how to do it using C#.
I tried the following it doesnt work.
NetworkCredential theNetworkCredential = new NetworkCredential(#"admin", "pass");
CredentialCache theNetCache = new CredentialCache();
theNetCache.Add(new Uri(#"E:\SDAVideo"), "Basic", theNetworkCredential);
string[] theFolders = Directory.GetDirectories(#"E:\SDAVideo");
Here is what I have tried.
Using following code I am able to get the Windows of Identity of Admin user account
SafeTokenHandle safeTokenHandle;
try
{
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser("admin", "", "pass",
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Debug.Write("\nLogonUser failed with error code : " + ret);
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
Debug.Write("\nDid LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
Debug.Write("\nValue of Windows NT token: " + safeTokenHandle);
// Check the identity.
Debug.Write("\nBefore impersonation: "
+ WindowsIdentity.GetCurrent().Name);
Debug.Write("\nWriteAccess before: " + DirectoryHasPermission(#"E:\SDAVideo", WindowsIdentity.GetCurrent(), FileSystemRights.Write));
// Use the token handle returned by LogonUser.
using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(safeTokenHandle.DangerousGetHandle()))
{
// Check the identity.
Debug.Write("\nAfter impersonation: "
+ WindowsIdentity.GetCurrent().Name);
AccessFolder(WindowsIdentity.GetCurrent(), #"E:\SDAVideo");
//Debug.Write("\nWriteAccess after: " + DirectoryHasPermission(#"E:\SDAVideo", WindowsIdentity.GetCurrent(), FileSystemRights.Write));
}
// Releasing the context object stops the impersonation
// Check the identity.
Debug.Write("\nAfter closing the context: " + WindowsIdentity.GetCurrent().Name);
}
}
catch (Exception ex)
{
Debug.Write("\n1" + ex.ToString());
}
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{
}
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
public static bool DirectoryHasPermission(string DirectoryPath, WindowsIdentity identity, FileSystemRights AccessRight)
{
//if (string.IsNullOrEmpty(DirectoryPath)) return false;
try
{
AuthorizationRuleCollection rules = Directory.GetAccessControl(DirectoryPath).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
//WindowsIdentity identity = WindowsIdentity.GetCurrent();
Debug.Write("\nUSER: " + identity.Name);
foreach (FileSystemAccessRule rule in rules)
{
if (identity.Groups.Contains(rule.IdentityReference))
{
if ((AccessRight & rule.FileSystemRights) == AccessRight)
{
if (rule.AccessControlType == AccessControlType.Allow)
return true;
}
}
}
}
catch (Exception ex)
{
Debug.Write("\n2 " + ex.ToString());
}
return false;
}
public static void AccessFolder(WindowsIdentity identity, string DirectoryPath)
{
try
{
Debug.Write("\nUSER: " + identity.Name);
DirectoryInfo myDirectoryInfo = new DirectoryInfo(DirectoryPath);
DirectorySecurity myDirectorySecurity = myDirectoryInfo.GetAccessControl();
myDirectorySecurity.AddAccessRule(new FileSystemAccessRule(identity.Name, FileSystemRights.Write, AccessControlType.Allow));
myDirectoryInfo.SetAccessControl(myDirectorySecurity);
string pathString = System.IO.Path.Combine(DirectoryPath, "test.txt");
if (!System.IO.File.Exists(pathString))
{
using (System.IO.FileStream fs = System.IO.File.Create(pathString))
{
for (byte i = 0; i < 100; i++)
{
fs.WriteByte(i);
}
}
}
}
catch (Exception ex)
{
Debug.Write("\n3 " + ex.ToString());
}
}
Error I get as follows
System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.
System.InvalidOperationException: Method failed with unexpected error code 1346.
All of these failed, Only thing I am able to do is successfully get the Admin user windows identity.
Now with that I need to solve the two issues
1. How to check if app has write access using the windows identity of the admin
2. If so how to write something into the folder.
Try that:
using System;
using System.IO;
using System.Security.AccessControl;
namespace MyWpfApplication
{
public class AccessRules
{
private void SetAccessRuleForCurrentUser()
{
DirectoryInfo myDirectoryInfo = new DirectoryInfo(#"e:\Folder1");
DirectorySecurity myDirectorySecurity = myDirectoryInfo.GetAccessControl();
myDirectorySecurity.AddAccessRule(new FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, FileSystemRights.Read, AccessControlType.Allow));
myDirectoryInfo.SetAccessControl(myDirectorySecurity);
}
}
}

Verify password complexity against Active Directory in C#

I am writing an installer that installs SQL, beforehand a user is prompted to enter the SA username/password which will be created for them. When SQL installs it verifies this password against the Active Directory policy and will fail if it doesnt match.
What I want to do is verify the password input by the user is valid before proceeding to install SQL.
How can I validate a password is correct against Active Directory rules?
Note I do not have a login to verify as per this answer, but simply a password to verify.
I am currently trying this, but writing "password" which I know is not allowed doesnt throw an exception
try
{
System.DirectoryServices.DirectoryEntry localMachine = new System.DirectoryServices.DirectoryEntry("WinNT://" + Environment.MachineName);
ListPasswordPolicyInfo(Environment.MachineName);
System.DirectoryServices.DirectoryEntry newUser = localMachine.Children.Add("localuser", "user");
newUser.Invoke("SetPassword", new object[] { "3l!teP#$$w0RDz" });
newUser.Invoke("SetPassword", new object[] { "password" });
//newUser.CommitChanges();
//Console.WriteLine(newUser.Guid.ToString());
localMachine.Close();
newUser.Close();
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
After a lot of pain I have found the C# solution to this using NetValidatePasswordPolicy. Use the supporting structs off of PInvoke and the following code
public static NET_API_STATUS ValidatePassword(string password)
{
var outputArgs = new NET_VALIDATE_OUTPUT_ARG();
var inputArgs = new NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG();
IntPtr inputPointer = IntPtr.Zero;
IntPtr outputPointer = IntPtr.Zero;
try
{
inputArgs.PasswordMatched = true;
inputArgs.ClearPassword = Marshal.StringToBSTR(password);
// If using a secure string
////inputArgs.ClearPassword = Marshal.SecureStringToBSTR(secureStringPassword);
inputPointer = Marshal.AllocHGlobal(Marshal.SizeOf(inputArgs));
Marshal.StructureToPtr(inputArgs, inputPointer, false);
NET_API_STATUS status = NetValidatePasswordPolicy(System.Environment.MachineName, IntPtr.Zero, NET_VALIDATE_PASSWORD_TYPE.NetValidatePasswordChange, inputPointer, ref outputPointer);
if (status == NET_API_STATUS.NERR_Success)
{
outputArgs = (NET_VALIDATE_OUTPUT_ARG)Marshal.PtrToStructure(outputPointer, typeof(NET_VALIDATE_OUTPUT_ARG));
if (outputArgs.ValidationStatus == NET_API_STATUS.NERR_Success)
{
// Ok
}
return outputArgs.ValidationStatus;
}
else
{
return status;
}
}
finally
{
if (outputPointer != IntPtr.Zero)
{
NetValidatePasswordPolicyFree(ref outputPointer);
}
if (inputArgs.ClearPassword != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(inputArgs.ClearPassword);
}
if (inputPointer != IntPtr.Zero)
{
Marshal.FreeHGlobal(inputPointer);
}
}
}
try{
var userName = "bob";
using (var pc = new PrincipalContext(ContextType.Domain)
{
var user = UserPrincipal.FindByIdentity(pc, userName);
user.ChangePassword(oldpassword, newpassword); //Checks password policy
//or
user.SetPassword(newpassword); //Not positive checks password policy but I believe it 2.
}
}
catch(PasswordException ex)
{
//do something with ex
}

how to change password to user account, by c# code?

how to change password to user account, by c# code?
Here is a simpler way to do this, however you will need to reference System.DirectoryServices.AccountManagement from .Net 4.0
namespace PasswordChanger
{
using System;
using System.DirectoryServices.AccountManagement;
class Program
{
static void Main(string[] args)
{
ChangePassword("domain", "user", "oldpassword", "newpassword");
}
public static void ChangePassword(string domain, string userName, string oldPassword, string newPassword)
{
try
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
using (var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, userName))
{
user.ChangePassword(oldPassword, newPassword);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
Using active directory:
// Connect to Active Directory and get the DirectoryEntry object.
// Note, ADPath is an Active Directory path pointing to a user. You would have created this
// path by calling a GetUser() function, which searches AD for the specified user
// and returns its DirectoryEntry object or path. See http://www.primaryobjects.com/CMS/Article61.aspx
DirectoryEntry oDE;
oDE = new DirectoryEntry(ADPath, ADUser, ADPassword, AuthenticationTypes.Secure);
try
{
// Change the password.
oDE.Invoke("ChangePassword", new object[]{strOldPassword, strNewPassword});
}
catch (Exception excep)
{
Debug.WriteLine("Error changing password. Reason: " + excep.Message);
}
Here you have example to change it in the local user account:
http://msdn.microsoft.com/en-us/library/ms817839
Other alternative could be using interoperability and call unmanaged code: netapi32.dll
http://msdn.microsoft.com/en-us/library/aa370650(VS.85).aspx
DirectoryEntry AD = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntry grp;
grp = AD.Children.Find("test", "user");
if (grp != null)
{
grp.Invoke("SetPassword", new object[] { "test" });
}
grp.CommitChanges();
MessageBox.Show("Account Change password Successfully");
"run in administrator to change all user
This works for both AD and Local Accounts.
If you want to invoke this API via C#, you may use this signature to import API NetUserChangePassword to your C# code:
[DllImport("netapi32.dll", CharSet=CharSet.Unicode, CallingConvention=CallingConvention.StdCall,SetLastError=true )]
static extern uint NetUserChangePassword (
[MarshalAs(UnmanagedType.LPWStr)] string domainname,
[MarshalAs(UnmanagedType.LPWStr)] string username,
[MarshalAs(UnmanagedType.LPWStr)] string oldpassword,
[MarshalAs(UnmanagedType.LPWStr)] string newpassword);

Categories