I'm trying to validate a username and its password on a local windows machine.
bool AreUserCredentialsValid(string username, string password)
{
try
{
using(var context = new PrincipalContext(ContextType.Machine))
{
return context.ValidateCredentials(username, password);
}
}
catch
{
return false;
}
}
This works fine for most cases. In one case I get the following error message.
The user cannot login because of an account restriction. Possible reasons are blank passwords not allowed, logon hour restrictions, or a policy.
This message appears only if I login as user A and try to validate the credentials of user A. If I login as user B and try to validate the credentials of user A it works as expected.
I can reproduce this behavoir with the following cmd line.
runas /user:A notepad
How can I validate credentials of a local user in any case without changing system settings or the user? As the user is able to login it should be possible.
Related
I have created two test accounts test and test1.
I am able to login to one local windows user account "test" using the following code.
bool result = false;
ContextType contextType = ContextType.Machine;
//if (InDomain())
//{
// contextType = ContextType.Domain;
//}
try
{
using (PrincipalContext principalContext = new PrincipalContext(contextType))
{
result = principalContext.ValidateCredentials(
userName,
new NetworkCredential(string.Empty, password).Password
);
}
}
catch (PrincipalOperationException)
{
// Account disabled? Considering as Login failed
result = false;
}
catch (Exception)
{
throw;
}
return result;
However when I try to login with another account "test1", it throws the following exception.
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again
It seems to me that the previous user "test" is already connected and occupies the resources, that is why it is not letting the user "test1" to get logged in and acquire the resources.
I have tried to find a way so that the first user "test" can be disconnected (or logged off) so that the 2nd user can get logged in.
I had tried to access the UserPrinciple through following code to get the connected user to disconnect but there is no method available in UserPrinciple
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, "test");
There must be some way to log off (or disconnect) the logged in user.
I don't know whether there's a nicer solution, but you could try a workaround using the net use command. By hand:
Run net use
Identify the relevant connections
Run net use /d <connection> for all relevant connections
Connect using the new user
I try to authenticate users belonging to remote ActiveDirectory from my machine, which is not the same domain as the current machine or user domain. There will be no trust between my machine and remote ActiveDirectory machine.
Initial Try
I tried to authenticate a user(Input: sAMAccountName, machine's ipaddress, machine's domain username("Administrator") and machine's password(***). Able to get result that the user with 'sAMAccountName' do exist in ActiveDirectory.
My Requirement:
Imagine that already a user("qwerty") is created in ActiveDirectory
From my local machine, I will have the following information,
a. Remote ActiveDirectory ipaddress
b. Remote ActiveDirectory machine's username and password.
c. Username and password of User "qwerty"
I need to check whether User "qwerty" is present in remote ActiveDirectory's users list and validate whether the password entered is same in ActiveDirectory's Users list
Code I tried:
DirectoryEntry entry = new DirectoryEntry("LDAP://ipaddress/DC=dinesh,DC=com", name, password);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(sAMAccountName=" + name + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
isValid = true;
adsEntry.Close();
}
catch (Exception ex)
{
adsEntry.Close();
}
Do I need to create a trust between local machine and remote ActiveDirectory machine before validating Users in a remote ActiveDirectory? If yes please tell how it can be done;
After creating trust, how can I validate Users?
===========================================================================
I am able to use the solution suggested by Rainer, but with a new problem. When I create a new user via C# code from a different machine, then some properties do not set properly.
Does this need to be set compulsorily while creating user?
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 :-)
I'm trying to check if the username/password for a remote computer, entered by a user on a WPF form are correct.
I have those strings: username, password and ip address.
I saw something about about "DirectoryEntry" but couldn't get it to work - the user is always authenticated even when the password is incorrect.
Any ideas?
There are multiple ways, but the way I've done it before is like this (using DirectoryEntry), it goes like this:
string ldapConnectionString = #"LDAP://[domain_server]/CN=Users,DC=[domain]"
using (var de = new DirectoryEntry(
ldapConnectionString, "username", "password",
AuthenticationTypes.Secure))
{
return de.NativeObject != null; // if not null -> user is valid
}
Edit: What this code will do is, validate a combination of a username/password against active directory. I think I misunderstood you (if what you mean is, to see if a user CAN connect to a particular server -> as in HAS PERMISSION to, I'm not quite sure how to do that, or even if it's possible).
I am working on a simple solution to update a user's password in Active Directory.
I can successfully update the users password. Updating the password works fine. Lets say the user has updated the password from MyPass1 to MyPass2
Now when I run my custom code to validate users credential using:
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "MyPass2");
}
//returns true - which is good
Now when I enter some wrong password it validates very nicely:
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "wrongPass");
}
//returns false - which is good
Now for some odd reasons, it validates the previous last password which was MyPass1 remember?
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "MyPass1");
}
//returns true - but why? we have updated password to Mypass2
I got this code from:
Validate a username and password against Active Directory?
Is it something to do with last password expiry or is this how the validation supposed to work?
The reason why you are seeing this has to do with special behavior specific to NTLM network authentication.
Calling the ValidateCredentials method on a PrincipalContext instance results in a secure LDAP connection being made, followed by a bind operation being performed on that connection using a ldap_bind_s function call.
The authentication method used when calling ValidateCredentials is AuthType.Negotiate. Using this results in the bind operation being attempted using Kerberos, which (being not NTLM of course) will not exhibit the special behavior described above. However, the bind attempt using Kerberos will fail (the password being wrong and all), which will result in another attempt being made, this time using NTLM.
You have two ways to approach this:
Follow the instructions in the Microsoft KB article I linked to shorten or eliminate the lifetime period of an old password using the OldPasswordAllowedPeriod registry value. Probably not the most ideal solution.
Don't use PrincipleContext class to validate credentials. Now that you know (roughly) how ValidateCredentials works, it shouldn't be too difficult for you to do the process manually. What you'll want to do is create a new LDAP connection (LdapConnection), set its network credentials, set the AuthType explicitly to AuthType.Kerberos, and then call Bind(). You'll get an exception if the credentials are bad.
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;
}
I try to never use exceptions to handle the flow control of my code; however, in this particular instance, the only way to test credentials on a LDAP connection seems to be to attempt a Bind operation, which will throw an exception if the credentials are bad. PrincipalContext takes the same approach.
I've found a way to validate the user's current credentials only. It leverages the fact that ChangePassword does not use cached credentials. By attempting to change the password to its current value, which first validates the password, we can determine if the password is incorrect or there is a policy problem (can't reuse the same password twice).
Note: this will probably only work if your policy has a history requirement of at least not allowing to repeat the most recent password.
var isPasswordValid = PrincipalContext.ValidateCredentials(
userName,
password);
// use ChangePassword to test credentials as it doesn't use caching, unlike ValidateCredentials
if (isPasswordValid)
{
try
{
user.ChangePassword(password, password);
}
catch (PasswordException ex)
{
if (ex.InnerException != null && ex.InnerException.HResult == -2147024810)
{
// Password is wrong - must be using a cached password
isPasswordValid = false;
}
else
{
// Password policy problem - this is expected, as we can't change a password to itself for history reasons
}
}
catch (Exception)
{
// ignored, we only want to check wrong password. Other AD related exceptions should occure in ValidateCredentials
}
}
Depending on the context of how you run this, it may have to do with something called "cached credentials."
I am having some difficulty with doing an automated login for users in my desktop Active Directory application. I may be trying to do an SSO, but I am under the impression that is only for web applications.
The code I have, is this:
PrincipalContext theContext = new PrincipalContext(ContextType.Domain);
if (theContext.ValidateCredentials(null, null))
Console.WriteLine("Credentials have been validated. Tell your friends.");
else
Console.WriteLine("Invalid credentials");
UserPrincipal user = new UserPrincipal(theContext, "uatu", "Passw0rd", true);
user.Save();
The PrincipalContext is being created without error, and I am validating the credentials. I assumed this would validate me as the user that logged in to the computer, which is under the Active Directory domain. And I can find users and groups. But as soon as I call user.Save() I get the error "Access is denied." Am I actually getting into Active Directory as a guest user?
If I set the user name and password in ValidateCredentials, it doesn't help.
PrincipalContext theContext = new PrincipalContext(ContextType.Domain);
if (theContext.ValidateCredentials("<username>", "<password", ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing))
Console.WriteLine("Credentials have been validated. Tell your friends.");
else
Console.WriteLine("Invalid credentials");
UserPrincipal user = new UserPrincipal(theContext, "uatu", "Passw0rd", true);
user.Save();
That code still fails on user.Save().
If I explicitly set the username and password to match myself as the logged in user in the PrincipalContext constructor, then I get success.
PrinicipalContext theContext = new PrincipalContext(ContextType.Domain,"<address>", "<domain context>", "<username>", "<password>");
UserPrincipal user = new UserPrincipal(theContext, "uatu", "Passw0rd", true);
user.Save();
That code succeeds. But I would rather not have the user log in to my application after they have logged into their computer with the exact same credentials.
I have been hearing a bit about "Affiliate Application", so I'm wondering if I have to let Active Directory know that it can trust my application. I am still hazy on the details through, and don't know if that is the wrong direction.
Does anyone have an idea as to what I should be doing?
If you are trying to modify UserPrincipals, you have a couple options:
User is already authenticated to windows as a user with permission to edit active directory:
Use the Constructor for PrincipalContext which doesn't take username/password
This will run the context as the currently logged in user
Run query, var usr = UserPrincipal.FindByIdentity(ctx, "bob#domain.local");
Perform manipulations on usr object
Call usr.Save(); on the returned user from the query.
User is authenticated to windows, but you must "impersonate" a user with AD permission
Use the Constructor for PrincipalContext which takes username/password
This will run the context as the credentials you passed in
Run query, var usr = UserPrincipal.FindByIdentity(ctx, "bob#domain.local");
Perform manipulations on usr object
Call usr.Save(); on the returned user from the query.
Based on your explanation above, I'm presuming you need option #1. ValidateCredentials(); is only used to validate credentials, it returns a true/false if the credentials you've given it are valid. Calling it has no lasting affect, it only validates. If you need to impersonate a user, you need to use the PrincipalContext constructor which takes credentials.