Verify password complexity against Active Directory in C# - 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
}

Related

Add, access and read Generic Credentials for a Specific User in .net Application

i want to add generic credentials to Credential Manager for a specific user. I tried using impersonation but it gives error "A specified logon session does not exist. It may already have been terminated". When i do not use "impersonation", I can add credentials easily but only for the user that opens the application. Anybody have any suggestion or answer to solve this problem?
My code fragment:
using (var imp = new Impersonation("creduserApp", "creduser", "123*+56"))
{
//I got error after running the following here
foreach (var credential in CredentialManager.EnumerateCrendentials())
{
Console.WriteLine(credential);
}
}
Impersonation Method:
public Impersonation(string domain, string username, string password)
{
var token = ValidateParametersAndGetFirstLoginToken(username, domain, password);
var duplicateToken = IntPtr.Zero;
try
{
if (DuplicateToken(token, 2, ref duplicateToken) == 0)
{
throw new InvalidOperationException("DuplicateToken call to reset permissions for this token failed");
}
var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
_impersonatedUserContext = identityForLoggedOnUser.Impersonate();
if (_impersonatedUserContext == null)
{
throw new InvalidOperationException("WindowsIdentity.Impersonate() failed");
}
}
finally
{
if (token != IntPtr.Zero)
CloseHandle(token);
if (duplicateToken != IntPtr.Zero)
CloseHandle(duplicateToken);
}
}

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

How to programmatically change Active Directory password

I have a set of test accounts that are going to be created but the accounts will be setup to require password change on the first login. I want to write a program in C# to go through the test accounts and change the passwords.
You can use the UserPrincipal class' SetPassword method, provided you have enough privileges, once you've found the correct UserPrincipal object. Use FindByIdentity to look up the principal object in question.
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var user = UserPrincipal.FindByIdentity( context, IdentityType.SamAccountName, userName ))
{
user.SetPassword( "newpassword" );
// or
user.ChangePassword( "oldPassword", "newpassword" );
user.Save();
}
}
Here's a great Active Directory programming quick reference:
Howto: (Almost) Everything In Active Directory via C#
See the password reset code near the end.
public void ResetPassword(string userDn, string password)
{
DirectoryEntry uEntry = new DirectoryEntry(userDn);
uEntry.Invoke("SetPassword", new object[] { password });
uEntry.Properties["LockOutTime"].Value = 0; //unlock account
uEntry.Close();
}
Try this code. It works for me,
public void ChangeMyPassword(string domainName, string userName, string currentPassword, string newPassword)
{
try
{
string ldapPath = "LDAP://192.168.1.xx";
DirectoryEntry directionEntry = new DirectoryEntry(ldapPath, domainName + "\\" + userName, currentPassword);
if (directionEntry != null)
{
DirectorySearcher search = new DirectorySearcher(directionEntry);
search.Filter = "(SAMAccountName=" + userName + ")";
SearchResult result = search.FindOne();
if (result != null)
{
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
userEntry.Invoke("ChangePassword", new object[] { currentPassword, newPassword });
userEntry.CommitChanges();
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
Here is the solution:
string newPassword = Membership.GeneratePassword(12, 4);
string quotePwd = String.Format(#"""{0}""", newPassword);
byte[] pwdBin = System.Text.Encoding.Unicode.GetBytes(quotePwd);
UserEntry.Properties["unicodePwd"].Value = pwdBin;
UserEntry.CommitChanges();
It is possible to set a new password to a domain account, by using .NET Framework 2.0.
See working code bellow:
string domainfqdn="mydomain.test.gov" //fqdn of the domain
string ldapPath =GetObjectDistinguishedName (objectClass.user,returnType.distinguishedName, args[0].ToString(),domainfqdn);
ldapPath="LDAP://" + domainfqdn + :389/"+ldapPath;
DirectoryEntry uEntry = new DirectoryEntry(ldapPath,null,null,AuthenticationTypes.Secure);
uEntry.CommitChanges();
Console.WriteLine(ldapPath);
string password="myS3cr3tPass"
uEntry.Invoke("SetPassword", new object[] { password });
uEntry.Properties["LockOutTime"].Value = 0; //unlock account
uEntry.CommitChanges();
uEntry.Close();
it is very importan to check the parameters at uEntry, the code will run under the current thread security context, unless the null values are specified
public void ResetPassword(string userName, string Password, string newPassword)
{
try
{
DirectoryEntry directoryEntry = new DirectoryEntry(Path, userName, Password);
if (directoryEntry != null)
{
DirectorySearcher searchEntry = new DirectorySearcher(directoryEntry);
searchEntry.Filter = "(samaccountname=" + userName + ")";
SearchResult result = searchEntry.FindOne();
if (result != null)
{
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
userEntry.Invoke("SetPassword", new object[] { newPassword });
userEntry.Properties["lockouttime"].Value = 0;
}
}
}
}
catch (Exception ex)
{
Log.Error("Password Can't Change:" + ex.InnerException.Message);
}
}

Validate a username and password against Active Directory?

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

Categories