PrincipalContext.ValidateCredentials with cached credentials in C# - c#

I'm trying to authenticate users in an application using a domain controller with code like this:
PrincipalContext pcon = new PrincipalContext(ContextType.Domain, domain);
password_ok = pcon.ValidateCredentials(username, password);
That works fine as long as the domain server is online but if there is no domain server the previous code fails with a PrincipalServerDownException exception.
What I'm trying to do is to make my application use the windows cached credentials if the server is down just like Windows do: you may login into windows with a domain user even if the domain server is down.
Can I do that in C#?
Thank you very much in advance.

For posterity, this is how I solved my problem:
bool dmainNotAvailable = false;
PrincipalContext pcon = null;
try
{
pcon = new PrincipalContext(ContextType.Domain, domain);
}
catch (System.DirectoryServices.AccountManagement.PrincipalServerDownException ex)
{
domainNotAvailable = true;
try
{
pcon = new PrincipalContext(ContextType.Machine, Environment.MachineName);
}
catch (Exception ex2)
{
throw new Exception(ex2);
}
}
string realUserName = !domainNotAvailable ? username : $"{domain}\\{username}";
passwordOk = pcon.ValidateCredentials(realUserName, password);
This will also work:
string userName = $"{domain}\\{user}";
PrincipalContext pcon = new PrincipalContext(ContextType.Machine, Environment.MachineName);
passworkOk = pcon.ValidateCredentials(userName, password);
The second solution will try lo login using the cached credentials and if there is no info then will try to login using the domain controller.
On both solutions the domain user must be added first to the local computer in order to have the credentials cached.
That's all.

Related

Given user id, password, and a subdomain name, how can I obtain the top-level domain name for a user account?

I'm writing a program (WinForms, C#) that runs on a Win 7 client machine. It obtains credentials from the user (user id, password, and subdomain name) and uses them to authenticate (via Active Directory) to other servers to which the program remotely connects. The other servers are on a domain different than the domain the Win 7 client machine is on.
Using the NetworkCredential, LdapDirectoryIdentifier, and LdapConnection classes, I can test the credentials with no more than the user id, password, and subdomain name (See answer for S.O. Why does Active Directory validate last password?).
For example, for the user account ssmith#xyz.gov, I need only provide ssmith (user id), the password for ssmith, and xyz (the subdomain). I don't need to provide the top-level domain name (gov in this case).
Now, I want to programmatically obtain the top-level domain name for this user account (gov in this case). I've examined the properties and methods of the NetworkCredential, LdapDirectoryIdentifier, and LdapConnection classes. I've looked over the other classes in the System.DirectoryServices.Protocols Namespace. I don't see a way to programmatically obtain the top-level domain name.
Given user id, password, and subdomain name, how can I obtain the top-level domain name for a user account?
Here is my code. Given a user account of ssmith#xyz.gov, My call looks like this (asterisks represent a SecureString password)
bool result = ValidateCredentials("ssmith","******", "xyz");
Here is my code for the method.
private const int ERROR_LOGON_FAILURE = 0x31;
private bool ValidateCredentials(string username, SecureString ssPassword, string domain)
{
//suports secure string
NetworkCredential credentials = new NetworkCredential(username, ssPassword, domain);
LdapDirectoryIdentifier id = new LdapDirectoryIdentifier(domain);
using (LdapConnection connection = new LdapConnection(id, credentials, AuthType.Kerberos))
{
connection.SessionOptions.Sealing = true;
connection.SessionOptions.Signing = true;
try
{
// 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
connection.Bind();
}
catch (LdapException lEx)
{
credentials = null;
id = null;
if (ERROR_LOGON_FAILURE == lEx.ErrorCode)
{
return false;
}
throw;
}
}
credentials = null;
id = null;
return true;
}
After a successful bind, then the full DNS name of the domain will be in the LdapConnection object:
var domain = connection.SessionOptions.DomainName;
In this case, that would be "xyz.gov". If you need just "gov", then you can just take everything after the last dot:
var tld = domain.Substring(domain.LastIndexOf('.') + 1);

System.DirectoryServices.AccountManagement.UserPrincipal - ChangePassword method throws exception

I am trying to implement a ChangePassword functionality on ActiveDirectory through the UserPrincipal. The code looks like this:
using System.DirectoryServices.AccountManagement;
private PrincipalContext Context { get; set; }
...
Context = new PrincipalContext(ContextType.Domain,
AdDomain,
AdRoot,
ContextOptions.SimpleBind,
AdUsername,
AdPassword)
...
public bool ChangePassword(string login, string password, string newPassword, out string message)
{
using (var foundUser = UserPrincipal.FindByIdentity(Context, IdentityType.SamAccountName, login))
{
try
{
foundUser.ChangePassword(password, newPassword);
foundUser.Save();
}
catch (Exception e)
{
message = e.Message;
return false;
}
return true;
}
}
When I try to test this on my Windows 10 machine I get an exception on ChangePassword, System.Runtime.InteropServices.COMException - One or more input parameters are invalid.
However, when I run this exact same code in the same project while connecting to the same AD domain on my Windows 7 machine it runs without error and changes the password. What could be causing this different behavior on the environments, and why does that error happen?
When the Context is created, make sure to set the ContextOptions to ContextOptions.Negotiate .
If you have mentioned ContextOptions.SimpleBind SetPassword may not work.
PrincipalContext oPrincipalContext = new PrincipalContext
(ContextType.Domain, "Name", "DefaultOU(if required)", ContextOptions.Negotiate,
"Service Account(if required)", "Service password");

connect active directory using c#

i m trying to connect to Active Directory code that i have used
string domain = "domain.com.pk";
string container = "DC=mycompnay,DC=com,DC=pk";
string Admin = "salman.zafar";
string Password = "password";
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain, container, Admin, Password))
{
string userPrincipalName = "dotnettest" + "#" + domain;
// validate the credentials
bool isValid = pc.ValidateCredentials(userPrincipalName, "Ascertia 12");
if (isValid) {
UserPrincipal up = UserPrincipal.FindByIdentity(pc, IdentityType.UserPrincipalName, userPrincipalName);
}
code works fine when the code running on machine which is in domain but if i try to connect to the AD machine that is remote then i get error
i tried to use
string domain = "192.168.0.150:389/domain.com.pk";
then it didn't work and validate credentials method always return false can some one help me how can i connect to remote active directory using IP with port with PrincipalContext or i have to use directory entry
any help will be appreciated
First note:
code works fine when the code running on machine which is in domain
In this case, you do not need to provide adminuser+pw in the PrincipalContext constructor if the machine is a domain member (which I assume here).
If you want to connect to any other AD server (domain controller) with no trust between the foreign domain and the current domain, use the IP address or server name as the "domain" name:
string domain = "192.168.0.150";
If your goal is to just check if credentials are valid, you can even omit the admin user + pw:
string domainController = "192.168.0.150";
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domainController))
{
string userPrincipalName = "dotnettest" + "#" + domain;
// validate the credentials
bool isValid = pc.ValidateCredentials(userPrincipalName, "Ascertia 12");
}
In this case, however, you cannot have
UserPrincipal up = UserPrincipal.FindByIdentity(...
because the PrincipalContext itself is not logged on.
You can also see my answer in a similar question: https://stackoverflow.com/a/28690682/4547223
or this SO article Validate a username and password against Active Directory?

The LDAP server is unavailable

I'm a total newbie to this
Trying to connect to an ldap server with PrincipalContext. I have tried all solutions on this site to no avail.
Things I've tried:
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Domain);
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Domain, "ldap://localhost:389/dc=maxcrc,dc=com");
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Domain, "maxcrc.com");
All give the same result:
LDAP server not available
Only ContextType.Machine works basically.
Not sure if my LDAP server is set up correctly:
Host: localhost
Port: 389
Base DN: dc=maxcrc,dc=com
URL: ldap://localhost:389/dc=maxcrc,dc=com
Testing with Softerra LDAP Browser
Any tutorials from start to finish will be much appreciated...
I have been facing the same issue and I found a solution.
I'm able to connect easily using following code:
ADUser_Id = "domainName\\username"; //make sure user name has domain name.
Password = "xxxx";
var context = new PrincipalContext(ContextType.Domain,"server_address", ADUser_Id,Password);
/* server_address = "192.168.15.36"; //don't include ldap in url */
I had similar issues. It turned out that I had to pass username and password in the object initialization. Please try using a statement like below:
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Domain,
"ldap://localhost:389/dc=maxcrc,dc=com",
userName,
password);
Also make sure that your username has domain in it.
For example,
userName = "mydomainname" + "\\" + "john_jacobs"
Use the following constructor overload for PrincipalContext:
public PrincipalContext(
ContextType contextType,
string name,
string container
)
And separate the server name from the LDAP string:
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Domain, "localhost:389", "dc=maxcrc,dc=com");
https://msdn.microsoft.com/en-us/library/bb348316%28v=vs.110%29.aspx
In my environment I had to create the principal context with just the domain controller host name, and then separately validate the user credentials.
string domainControllerName = "PDC";
string domainName = "MyDomain"; // leave out the .Local, this is just to use as the prefix for the username if the user left it off or didn't use the principal address notation
string username = "TestUser";
string password = "password";
using (var ldap = new PrincipalContext(ContextType.Domain, domainControllerName))
{
var usernameToValidate = username;
if (!usernameToValidate.Any(c => c == '#' || c == '\\'))
usernameToValidate = $"{domainName}\\{username}";
if (!ldap.ValidateCredentials(username, context.Password, ContextOptions.SimpleBind))
throw new UnauthorizedException();
}
This example allows for all three of these variations to the username to validate:
TestUser
MyDomain\TestUser
TestUser#MyDomain.Local
You may want to try your local machine address instead :
ldap://127.0.0.1:389/dc=maxcrc,dc=com
If that doesn't work, I'd fire up Wireshark, and have it capture traffic on port 389 as you're attempting to connect via Softerra.
In my time working with LDAP and .Net DirectoryServices, that error usually means the syntax or naming convention of the path is incorrect, or does not point to a valid directory end point.
That error might be due to trying to connect as "Anonymous" without specifying it explicitly.
By default all connections are Negotiable. So if you try something like that you could try the following:
LdapDirectoryIdentifier ldap = new LdapDirectoryIdentifier("My Hostname or IP Address",10389); //10389 might be your non default port
LdapConnection connection = new LdapConnection(ldap);
connection.AuthType = AuthType.Anonymous;

Check exchange credentials remotely and check user logged in

I have attempted this with not much success. Basically I need to login to Exchange using EWS remotely.
The issue is I don't know if the user has logged in OK or if the credentials are wrong as I get nothing back! If I provide wrong credentials the software just carries on!
Is there something I'm missing, I've checked the MSDN stuff about EWS which shows you how to connect to exchange but nothing about validating credentials!
Below is the code I currently have to connect.
public void connect(string Email, string Password)
{
try
{
_useremail = Email;
_userpass = Password;
// Define the credentials to use.
var credentials = new WebCredentials(_useremail, _userpass);
_ExchangeServer = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
_ExchangeServer.Credentials = credentials;
_ExchangeServer.Url = new Uri(_ExchangeEWSURL);
_ExchangeServer.Timeout = 60;
_ExchangeServer.KeepAlive = true;
_ExchangeConnected = true;
}
catch (Exception ex)
{
_ExchangeConnected = false;
throw ex;
}
}
as you can see at present I just set a bool value to true in the class. Any ideas?
In order to check whether the given credentials are valid, you must query resources you expect the user to have access to (calendar, inbox, contacts, etc.). There is no explicit login method - the authentication occurs implicitly when you request user resources (via FindItems, FindFolders, FindAppointments, etc.).

Categories