I want to validate a set of credentials against the domain controller. e.g.:
Username: STACKOVERFLOW\joel
Password: splotchy
Method 1. Query Active Directory with Impersonation
A lot of people suggest querying the Active Directory for something. If an exception is thrown, then you know the credentials are not valid - as is suggested in this stackoverflow question.
There are some serious drawbacks to this approach however:
You are not only authenticating a domain account, but you are also doing an implicit authorization check. That is, you are reading properties from the AD using an impersonation token. What if the otherwise valid account has no rights to read from the AD? By default all users have read access, but domain policies can be set to disable access permissions for restricted accounts (and or groups).
Binding against the AD has a serious overhead, the AD schema cache has to be loaded at the client (ADSI cache in the ADSI provider used by DirectoryServices). This is both network, and AD server, resource consuming - and is too expensive for a simple operation like authenticating a user account.
You're relying on an exception failure for a non-exceptional case, and assuming that means invalid username and password. Other problems (e.g. network failure, AD connectivity failure, memory allocation error, etc) are then mis-intrepreted as authentication failure.
Method 2. LogonUser Win32 API
Others have suggested using the LogonUser()
API function. This sounds nice, but unfortunately the calling user sometimes needs a permission usually only given to the operating system itself:
The process calling LogonUser requires
the SE_TCB_NAME privilege. If the
calling process does not have this
privilege, LogonUser fails and
GetLastError returns
ERROR_PRIVILEGE_NOT_HELD.
In some
cases, the process that calls
LogonUser must also have the
SE_CHANGE_NOTIFY_NAME privilege
enabled; otherwise, LogonUser fails
and GetLastError returns
ERROR_ACCESS_DENIED. This privilege is
not required for the local system
account or accounts that are members
of the administrators group. By
default, SE_CHANGE_NOTIFY_NAME is
enabled for all users, but some
administrators may disable it for
everyone.
Handing out the "Act as a part of the operating system" privilege is not something you want to do willy-nilly - as Microsoft points out in a knowledge base article:
...the process that is calling
LogonUser must have the SE_TCB_NAME
privilege (in User Manager, this is
the "Act as part of the Operating
System" right). The SE_TCB_NAME
privilege is very powerful and
should not be granted to any arbitrary user just so that they can
run an application that needs to
validate credentials.
Additionally, a call to LogonUser()
will fail if a blank password is specified.
What is the proper way to authenticate a set of domain credentials?
I happen to be calling from managed code, but this is a a general Windows question. It can be assumed that the customers have the .NET Framework 2.0 installed.
C# in .NET 3.5 using System.DirectoryServices.AccountManagement.
bool valid = false;
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
valid = context.ValidateCredentials( username, password );
}
This will validate against the current domain. Check out the parameterized PrincipalContext constructor for other options.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.DirectoryServices.AccountManagement;
public struct Credentials
{
public string Username;
public string Password;
}
public class Domain_Authentication
{
public Credentials Credentials;
public string Domain;
public Domain_Authentication(string Username, string Password, string SDomain)
{
Credentials.Username = Username;
Credentials.Password = Password;
Domain = SDomain;
}
public bool IsValid()
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
{
// validate the credentials
return pc.ValidateCredentials(Credentials.Username, Credentials.Password);
}
}
}
I`m using the following code to validate credentials.
The method shown below will confirm if the credentials are correct and if not wether the password is expired or needs change.
I`ve been looking for something like this for ages... So i hope this helps someone!
using System;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Runtime.InteropServices;
namespace User
{
public static class UserValidation
{
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(string principal, string authority, string password, LogonTypes logonType, LogonProviders logonProvider, out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
enum LogonProviders : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
enum LogonTypes : uint
{
Interactive = 2,
Network = 3,
Batch = 4,
Service = 5,
Unlock = 7,
NetworkCleartext = 8,
NewCredentials = 9
}
public const int ERROR_PASSWORD_MUST_CHANGE = 1907;
public const int ERROR_LOGON_FAILURE = 1326;
public const int ERROR_ACCOUNT_RESTRICTION = 1327;
public const int ERROR_ACCOUNT_DISABLED = 1331;
public const int ERROR_INVALID_LOGON_HOURS = 1328;
public const int ERROR_NO_LOGON_SERVERS = 1311;
public const int ERROR_INVALID_WORKSTATION = 1329;
public const int ERROR_ACCOUNT_LOCKED_OUT = 1909; //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!!
public const int ERROR_ACCOUNT_EXPIRED = 1793;
public const int ERROR_PASSWORD_EXPIRED = 1330;
public static int CheckUserLogon(string username, string password, string domain_fqdn)
{
int errorCode = 0;
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain_fqdn, "ADMIN_USER", "PASSWORD"))
{
if (!pc.ValidateCredentials(username, password))
{
IntPtr token = new IntPtr();
try
{
if (!LogonUser(username, domain_fqdn, password, LogonTypes.Network, LogonProviders.Default, out token))
{
errorCode = Marshal.GetLastWin32Error();
}
}
catch (Exception)
{
throw;
}
finally
{
CloseHandle(token);
}
}
}
return errorCode;
}
}
Here's how to determine a local user:
public bool IsLocalUser()
{
return windowsIdentity.AuthenticationType == "NTLM";
}
Edit by Ian Boyd
You should not use NTLM anymore at all. It is so old, and so bad, that Microsoft's Application Verifier (which is used to catch common programming mistakes) will throw a warning if it detects you using NTLM.
Here's a chapter from the Application Verifier documentation about why they have a test if someone is mistakenly using NTLM:
Why the NTLM Plug-in is Needed
NTLM is an outdated authentication protocol with flaws that
potentially compromise the security of applications and the operating
system. The most important shortcoming is the lack of server
authentication, which could allow an attacker to trick users into
connecting to a spoofed server. As a corollary of missing server
authentication, applications using NTLM can also be vulnerable to a
type of attack known as a “reflection” attack. This latter allows an
attacker to hijack a user’s authentication conversation to a
legitimate server and use it to authenticate the attacker to the
user’s computer. NTLM’s vulnerabilities and ways of exploiting them
are the target of increasing research activity in the security
community.
Although Kerberos has been available for many years many applications
are still written to use NTLM only. This needlessly reduces the
security of applications. Kerberos cannot however replace NTLM in all
scenarios – principally those where a client needs to authenticate to
systems that are not joined to a domain (a home network perhaps being
the most common of these). The Negotiate security package allows a
backwards-compatible compromise that uses Kerberos whenever possible
and only reverts to NTLM when there is no other option. Switching code
to use Negotiate instead of NTLM will significantly increase the
security for our customers while introducing few or no application
compatibilities. Negotiate by itself is not a silver bullet – there
are cases where an attacker can force downgrade to NTLM but these are
significantly more difficult to exploit. However, one immediate
improvement is that applications written to use Negotiate correctly
are automatically immune to NTLM reflection attacks.
By way of a final word of caution against use of NTLM: in future
versions of Windows it will be possible to disable the use of NTLM at
the operating system. If applications have a hard dependency on NTLM
they will simply fail to authenticate when NTLM is disabled.
How the Plug-in Works
The Verifier plug detects the following errors:
The NTLM package is directly specified in the call to AcquireCredentialsHandle (or higher level wrapper API).
The target name in the call to InitializeSecurityContext is NULL.
The target name in the call to InitializeSecurityContext is not a properly-formed SPN, UPN or NetBIOS-style domain name.
The latter two cases will force Negotiate to fall back to NTLM either directly (the first case) or indirectly (the domain controller will return a “principal not found” error in the second case causing Negotiate to fall back).
The plug-in also logs warnings when it detects downgrades to NTLM; for example, when an SPN is not found by the Domain Controller. These are only logged as warnings since they are often legitimate cases – for example, when authenticating to a system that is not domain-joined.
NTLM Stops
5000 – Application Has Explicitly Selected NTLM Package
Severity – Error
The application or subsystem explicitly selects NTLM instead of Negotiate in the call to AcquireCredentialsHandle. Even though it may be possible for the client and server to authenticate using Kerberos this is prevented by the explicit selection of NTLM.
How to Fix this Error
The fix for this error is to select the Negotiate package in place of NTLM. How this is done will depend on the particular Network subsystem being used by the client or server. Some examples are given below. You should consult the documentation on the particular library or API set that you are using.
APIs(parameter) Used by Application Incorrect Value Correct Value
===================================== =============== ========================
AcquireCredentialsHandle (pszPackage) “NTLM” NEGOSSP_NAME “Negotiate”
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices.AccountManagement;
class WindowsCred
{
private const string SPLIT_1 = "\\";
public static bool ValidateW(string UserName, string Password)
{
bool valid = false;
string Domain = "";
if (UserName.IndexOf("\\") != -1)
{
string[] arrT = UserName.Split(SPLIT_1[0]);
Domain = arrT[0];
UserName = arrT[1];
}
if (Domain.Length == 0)
{
Domain = System.Environment.MachineName;
}
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, Domain))
{
valid = context.ValidateCredentials(UserName, Password);
}
return valid;
}
}
Kashif Mushtaq
Ottawa, Canada