Validate a Username and Password Against Active Directory

How to properly authenticate user against Active Directory?

ValidateCredentials takes a username without domain information. The domain should be defined when creating the PrincipalContext:

if (!username.Contains("@") && !username.Contains(@"\"))
{
// EXCEPTION
}

var domain = username.Contains("@") ? username.Split("@")[1].Split(".")[0] : username.Split(@"\")[0];
var principalContext = new PrincipalContext(ContextType.Domain, domain);

var user = username.Contains("@") ? username.Split("@")[0] : username.Split(@"\")[1];
var isValid = principalContext.ValidateCredentials(user, cleartextpw);


PrincipalContext

ValidateCredentials

Validate users of Remote Active Directory in C#

First some basics (independent of this question)

Authentication

The system checks if Bob is really Bob. In an Active Directory environment, this is usually done with a domain login from the workstation, Bob enters his username and password, and he gets a Kerberos ticket. Later, if he wants to access e.g. a file share on a remote fileserver, he does not need to login anymore, and can access the files without entering username/password.

Authorization

The system checks which resources Bob is allowed to access. Usually Bob is in domain groups, and a group is in the ACL (access control list) of the resource.

If there are multiple trusting domains, Bob needs to login in one domain, and can access resources in all other domains.
This is one of the main reasons using Active Directory: single sign on

Checking if user / password is valid

If you have a username and password and want to check if the password is valid, you have to do a login to the domain. There is no way of just “checking if the password is correct”.
Login means: if there is a security policy “lock account if more than 3 invalid logins”, the account will be locked out checking with wrong password, even if you “only want to check the user+password”.

Using .NET Directory Service functions

I assume here that the process is either run by a human account as a normal program, or the program is a Windows service or a scheduled task which runs under a domain “technical user” account. In this case, you do not need to provide credentials for using the AD functions. If accessing other trusting AD domains, this is also true.
If you want to login to a “foreign domain”, and there is no trust, you need to provide a username+password (as in your code).

"Manually" authenticating a user

Normally, this should not be needed. Example: ASP.NET intranet usage. The user access a web application on the current domain or trusting domain, the authentication is done “in the background” by browser and IIS (if integrated Windows authentication is on). So you never need to handle user passwords in the application.

I don’t see many use cases where a password is handled by code.

One may that your program is a helper tool for storing emergency user accounts/passwords. And you want to check periodically if these accounts are valid.

This is a simple way to check:

using System.DirectoryServices.AccountManagement;
...

PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "192.168.1.1");

bool userValid = principalContext.ValidateCredentials(name, password);

One can also use the older, raw ADSI functions:

using System.DirectoryServices;
....

bool userOk = false;
string realName = string.Empty;

using (DirectoryEntry directoryEntry =
new DirectoryEntry"LDAP://192.168.1.1/DC=ad,DC=local", name, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(directoryEntry))
{
searcher.Filter = "(samaccountname=" + name + ")";
searcher.PropertiesToLoad.Add("displayname");

SearchResult adsSearchResult = searcher.FindOne();

if (adsSearchResult != null)
{
if (adsSearchResult.Properties["displayname"].Count == 1)
{
realName = (string)adsSearchResult.Properties["displayname"][0];
}
userOk = true;
}
}
}

If your real requirement is actually a validity check of user+password, you can do it in one of these ways.

However, if it is a "normal application", which just wants to check if the entered credentials are valid, you should rethink your logic. In this case, you better should rely on the single sign on capabilities of AD.

If there are further questions, please comment.

b. Remote ActiveDirectory machine's username and password.

This sounds a bit unclear. I assume you mean "a username and corresponding password in the remote domain".

There is also the concept of a machine account, which is the hostname appended with $. But that's another topic.


Creating new user

Option 1

using (DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://192.168.1.1/CN=Users,DC=ad,DC=local", 
name, password))
{
using (DirectoryEntry newUser = directoryEntry.Children.Add("CN=CharlesBarker", "user"))
{
newUser.Properties["sAMAccountName"].Value = "CharlesBarker";
newUser.Properties["givenName"].Value = "Charles";
newUser.Properties["sn"].Value = "Barker";
newUser.Properties["displayName"].Value = "CharlesBarker";
newUser.Properties["userPrincipalName"].Value = "CharlesBarker";
newUser.CommitChanges();
}
}

Option 2

using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "192.168.1.1", 
"CN=Users,DC=ad,DC=local", name, password))
{
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext))
{
userPrincipal.Name = "CharlesBarker";
userPrincipal.SamAccountName = "CharlesBarker";
userPrincipal.GivenName = "Charles";
userPrincipal.Surname = "Barker";
userPrincipal.DisplayName = "CharlesBarker";
userPrincipal.UserPrincipalName = "CharlesBarker";
userPrincipal.Save();
}
}

I leave as an exercise to you to find out which attribute goes into which User dialog entry field :-)

C# Validate User against the MS Active Directory Server using System.DirectoryServices.Protocols

The highest voted answer in the second question you linked to has everything you need to know. The only way an old password is ever allowed is if NTLM is the authentication mechanism used. If you want to prevent that, you have to tell it to only use Kerberos and not NTLM:

var conn = new LdapConnection(serverId, credentials, AuthType.Kerberos);

I saw your comment on that answer, saying that with that, all authentication fails, even with the right password. That just means that Kerberos authentication isn't working. That's a whole other troubleshooting mess. If the server you are running this on is joined to the same (or trusted) domain as the user you are authenticating, then there really is no reason Kerberos shouldn't work. But if the server is outside of the domain, it can be troublesome to set up Kerberos.

Here's some reading on that:

  • Kerberos Troubleshooting
  • Troubleshooting Kerberos Authentication problems – Name resolution issues

Or you might decide to just ignore this and let it use NTLM. It only allows an old password for 1 hour after the change anyway.

How do I validate a username and password in Azure AD?

Without connection with Azure AD, you could not validate the user, also I think it is unnecessary to do that, if you get the token with AcquireTokenByUsernamePassword methond, it essentially uses the Azure AD ROPC flow, it will validate the user automatically for you, if the user is invalidated, it will give an error Error validating credentials due to invalid username or password.

Csharp Sample Image 4

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.



Related Topics



Leave a reply



Submit