How to validate domain credentials?
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.
How to validate domain credentials without considering the Cached Domain Credential
Question already has an answer Why does Active Directory validate last password?
Solution is to use a Kerberos authentication.
The following code shows how you can perform credential validation using only Kerberos. The authentication method at use will not fall back to NTLM in the event of failure.
private const int ERROR_LOGON_FAILURE = 0x31;
private bool ValidateCredentials(string username, string password, string domain)
{
NetworkCredential credentials
= new NetworkCredential(username, password, domain);
LdapDirectoryIdentifier id = new LdapDirectoryIdentifier(domain);
using(LdapConnection connection = new LdapConnection(id, credentials, AuthType.Kerberos))
{
connection.SessionOptions.Sealing = true;
connection.SessionOptions.Signing = true;
try
{
connection.Bind();
}
catch (LdapException lEx)
{
if (ERROR_LOGON_FAILURE == lEx.ErrorCode)
{
return false;
}
throw;
}
return true;
}
Validate credentials for remote domain
Here is my final version of this test function that works with Powershell version older than 5.1.
Function Test-Cred
{
[CmdletBinding()]
[OutputType([String])]
Param (
[Parameter(
Mandatory = $false,
ValueFromPipeLine = $true,
ValueFromPipelineByPropertyName = $true
)]
[Alias(
'PSCredential'
)]
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$Credentials
)
# Checking module
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$ContextName = 'remote_domain.com'
$Validated = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ContextType, $ContextName, $($Credentials.UserName),$($Credentials.GetNetworkCredential().Password)
If ($Validated.ConnectedServer)
{
Return "Authenticated"
}
Else
{
Return "Not authenticated"
}
}
Validate user credentials against domain controller in .net
For the sake of completeness, here my solution which seems to do exactly what I want:
public class WinApiDomainAuthenticator
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
public static IPrincipal Authenticate(String domainUser, String password)
{
var userToken = IntPtr.Zero;
var creds = new DomainAuthCredentials(domainUser, password);
if (! LogonUser(creds.Username,
creds.Domain,
creds.Password,
(int)LogonType.LOGON32_LOGON_BATCH,
(int)LogonProvider.LOGON32_PROVIDER_DEFAULT, out userToken))
{
var error = new Win32Exception(Marshal.GetLastWin32Error());
throw new SecurityException("Error while authenticating user", error);
}
var identity = new WindowsIdentity(userToken);
if (userToken != IntPtr.Zero)
CloseHandle(userToken);
return ConvertWindowsIdentityToGenericPrincipal(identity);
}
protected static IPrincipal ConvertWindowsIdentityToGenericPrincipal(WindowsIdentity windowsIdentity)
{
if (windowsIdentity == null)
return null;
// Identity in format DOMAIN\Username
var identity = new GenericIdentity(windowsIdentity.Name);
var groupNames = new string[0];
if (windowsIdentity.Groups != null)
{
// Array of Group-Names in format DOMAIN\Group
groupNames = windowsIdentity.Groups
.Select(gId => gId.Translate(typeof(NTAccount)))
.Select(gNt => gNt.ToString())
.ToArray();
}
var genericPrincipal = new GenericPrincipal(identity, groupNames);
return genericPrincipal;
}
protected class DomainAuthCredentials
{
public DomainAuthCredentials(String domainUser, String password)
{
Username = domainUser;
Password = password;
Domain = ".";
if (!domainUser.Contains(@"\"))
return;
var tokens = domainUser.Split(new char[] { '\\' }, 2);
Domain = tokens[0];
Username = tokens[1];
}
public DomainAuthCredentials()
{
Domain = String.Empty;
}
#region Properties
public String Domain { get; set; }
public String Username { get; set; }
public String Password { get; set; }
#endregion
}
}
The LogonType and LogonProvider enums reflect the definitions in "Winbase.h". I settled with LogonType.LOGON32_LOGON_BATCH instead of LogonType.LOGON32_LOGON_NETWORK because samba 3.4.X seems to have trouble with this type.
Related Topics
How to Ignore the Utf-8 Byte Order Marker in String Comparisons
How to Avoid "Too Many Parameters" Problem in API Design
How to Find All the Classes Which Implement a Given Interface
Differencebetween a C# Reference and a Pointer
Is Configurationmanager.Appsettings Available in .Net Core 2.0
How to Run Multiple SQL Commands in a Single SQL Connection
How to Display the Displayattribute.Description Attribute Value
Unload a Dll Loaded Using Dllimport
How to Take a Screenshot of a Wpf Control
How to Save Picturebox.Image to File
How to Pass Values Between Forms in C# Windows Application
Difference Between Lookup() and Dictionary(Of List())
How to Align Text in Columns Using Console.Writeline
One Class Per File Rule in .Net
.Net, Event Every Minute (On the Minute). Is a Timer the Best Option