I am using directory services in .Net for first time.I want to use it to authenticate against local users on my machine in an asp.net website application
The code is as follows
DirectoryEntry entry = new DirectoryEntry("WinNT://"+Environment.MachineName+"/administrator", txtBoxUserName.Text, txtBoxPWD.Text);
For some reason I get an exception of "System.Runtime.InteropServices.COMException" although I used the right user name and password for administrator user.
Any Help would be appreciated
Try this:
public bool IsAuthenticationValid(string userName, string password)
{
using (var context = new PrincipalContext(ContextType.Machine))
{
return context.ValidateCredentials(userName, password);
}
}
Edit: Forgot to mention you will need System.DirectoryServices and System.DirectoryServices.AccountManagement references.
If you hit the same error, please describe what environment you are running this code in. By that I mean command line, web app, etc. Just some more background to see if I can help you further.
Related
I have an intranet application that authenticates users against active directory. In general it works fine when testing locally(i.e. from my dev machine using VS 2017) or running it off of IIS in app server ("Browse *:80") until I try to access it using its URL from my local machine. Then whatever user id I use to get user's detail info, nothing is displayed.
Also, this app is to be accessible to users in certain groups so application checks logged in user's group membership.
Here is how I have it set up in IIS and different scenarios I tested with:
I set authentication to "Windows Authentication", disabled anonymous
authentication and enabled AS.NET Impersonation. This works fine when
running from app server using localhost but when trying to access
from my local machine and supplying user's user id to get his/her
detail info, no details are displayed (fails to get any info from
AD).
If I enable anonymous authentication and set it to "Application Pool Identity" (i.e. Network Services), it displays my custom
"Access Denied" page, presumably because this user is not part of
allowed group.
If I enable anonymous authentication and select "Specific User", supplying my credentials, then everything works fine, from app server or from my local machine, with one caveat: no matter who is accessing the site, it shows my name as logged in user.
I am stumped ad would appreciate some hints.
Update - Added code to get user info
Code to get user's identity:
WindowsIdentity wiUser = WindowsIdentity.GetCurrent();
string sID = wiUser.Name.ToUpper();
Code to get user's AD info:
static string adDomain = "www.xxx.yyy.zzz";
static string adContainer = "DC=www,DC=xxx,DC=yyy,DC=zzz";
public static DataTable getUserADInfoDT(string sSearchStr)
{
DataTable dtResults = new DataTable();
dtResults.Columns.Add("ID");
dtResults.Columns.Add("FirstName");
...
dtResults.Columns.Add("Zip");
string adDomain = string.Empty;
string adContainer = string.Empty;
// create domain context
PrincipalContext adPrincipalContext = new PrincipalContext(ContextType.Domain, adDomain, adContainer);
using (adPrincipalContext)
{
// define a "query-by-example" principal
UserPrincipal qbeUser = new UserPrincipal(adPrincipalContext);
qbeUser.SamAccountName = sSearchStr.Trim().ToUpper();
// create principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
PrincipalSearchResult<Principal> psr = srch.FindAll();
// find all matches
foreach (var found in psr)
{
DataRow dr = dtResults.NewRow();
UserPrincipal up = (UserPrincipal)found;
DirectoryEntry de = (DirectoryEntry)up.GetUnderlyingObject();
dr["ID"] = de.Properties["SAMAccountName"].Value.ToString().ToUpper();
if (de.Properties["givenName"].Value != null)
dr["FirstName"] = de.Properties["givenName"].Value.ToString();
...
if (de.Properties["postalCode"].Value != null)
dr["Zip"] = de.Properties["postalCode"].Value.ToString();
dtResults.Rows.Add(dr);
//de.Close();
}
return dtResults;
}
}
WindowsIdentity.GetCurrent() will get the user account that the current process is running under. If the application is running in IIS, that will usually be the user account that the app pool is running under - not the user that logged in to your application.
If you use IIS Express for debugging locally, then IIS Express is running under your user account, so you wouldn't see a difference. But on the server you will.
That said, if you have impersonation turned on and working properly, then WindowsIdentity.GetCurrent() should return the user that logged in - since impersonation means that your app is now pretending to be that person. So this might mean that impersonation isn't setup right. But maybe you don't even need it. I've never personally found a need to use impersonation.
To get the user that logged into your app, you should use HttpRequest.Current.User or possibly just HttpContext.User if your app is ASP.NET MVC.
And there is actually an easier way to get a DirectoryEntry object for the user, without having to search for the username. You can bind directly to the SID of the user, which is information you already have:
var user = new DirectoryEntry(
$"LDAP://<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
If your server is not joined to the same domain as your users, then you might need to include the domain name:
var user = new DirectoryEntry(
$"LDAP://www.xxx.yyy.zzz/<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
Something else to keep in mind: DirectoryEntry keeps a cache of all the Properties. When you access a property that it doesn't have in the cache already, it will go out to AD and ask for every attribute with a value. That's a lot of useless data going over the network if you only need 3 attributes. So you can tell it to specifically ask for only those 4 attributes before you use them:
user.RefreshCache(new[] {"SAMAccountName", "givenName", "postalCode"});
I talked about this, and other points, in an article I wrote: Active Directory: Better Performance
Update: To verify if IIS is actually enforcing authentication for a page, you can do this in PowerShell (update the URL to what you need - and this requires at least PowerShell 3):
try { (Invoke-WebRequest "http://example.com/the/page").StatusCode }
catch { $_.Exception.Response.StatusCode.Value__}
That will output the status code of the reply. You should see 401, since that is how IIS challenges the browser to send credentials. If you see 200, then Windows Authentication is not working for that page.
I do this in PowerShell, since Chrome's dev tools won't even show you the 401 challenge.
Since Microsoft release the two following patches, my C# code just stopped working and always sends back an error message (The system cannot contact a domain controller to service the authentication request. Please try again later.) when trying to change the password.
https://support.microsoft.com/en-us/kb/3167679
https://support.microsoft.com/en-us/kb/3177108
private void ChangePassword(string username, string oldPassword, string newPassword)
{
string machineName = Environment.MachineName;
string contextUser = String.Format(#"{0}\{1}", machineName, username);
using (PrincipalContext ctx = new PrincipalContext(ContextType.Machine, machineName, null, ContextOptions.Negotiate, contextUser, oldPassword))
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, username))
{
user.ChangePassword(oldPassword, newPassword);
}
}
If the submitted credential is not valid, i get a specific error message saying bad username or password so the submitted credential is valid.
I tried many scenarios and they all fail:
Use the user/password of the user i want to change its password for the PrincipalContext.
Use a different user/password for the PrincipalContext.
User a local admin user/password for the PrincipalContext.
None works.
Read a lot about this "new behavior" and i can't seem to find a way to change a valid, not locked, not disabled local user.
Those posts are similar to mine and it seems like some people got a workaround but only for Active Directory and not local user accounts.
C# Active Directory Invoke “ChangePassword” cannot contact domain
Changing Active Directory user passwords in c#/asp.net after MS patch KB3167679
c# Change AD password Directoryservices
Can anyone tell me what's wrong with my code and explain me why it broke since the patches please?
If you just want to change the password for a local user you should be able to use NetUserChangePassword.
That still works for me. At least for the local machine (use the name of that as the domain name parameter).
Microsoft has updated this article: https://support.microsoft.com/en-us/kb/3177108 . Here they have given us problems created by the original "fixes" as well as some tips for working with Kerberos and self-service password reset.
As of October 11, 2016 Microsoft re-released the patches associated with https://technet.microsoft.com/en-us/library/security/ms16-101.aspx to resolve issues caused by the original updates (which you can read in https://support.microsoft.com/en-us/kb/3177108 including the fact that you could no longer change passwords on local accounts-- see "known issue #3").
So in short you should be able to change local passwords now with the latest updates (October 11, 2016).
I've created a small application which attempts to authenticate a user based on their username and password. This application works correctly when run on the same domain which Active Directory resides on.
I must now extend the application to also work on domains which are "closed" in terms of security and permissions. In other words, is there a way to run the application based on an administrator account, or an account which has the necessary permissions to access the Active Directory?
This is the code I have used to authenticate a user:
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, server + ":" + port))
{
if (pc.ValidateCredentials(username, password))
{
valid = true;
}
else
{
valid = false;
}
}
The above code works perfectly, however I would like to modify it so that it can communicate with the Active Directory database in an authenticated manner.
I have read numerous documentation and resources, but have not found anything. The closes I found was an article mentioning that IIS has to be set up and configured in a specific manner. However, my application is a simple C# application, and IIS is not being used.
If I understand the question properly, you want to execute ValidateCredentials using a different user than the current process' user.
I may be missing something, but have you tried modifying your code this way?
using (PrincipalContext pc =
new PrincipalContext(ContextType.Domain,
server + ":" + port,
specialAccountUsername,
specialAccountPassword))
{
return pc.ValidateCredentials(username, password);
}
It simply uses a constructor that takes the special account you are using for accessing the domain.
I am using the solution discussed here to authenticate users against the active directory in my ASP.NET web application. I've written a simple ADMembershipProvider class thas is used together with FormsAuthentication. Works fine when running the project locally but when deployed to a server in the network, the ValidateUser call takes really long time (approx 20s).
//Assumes using System.DirectoryServices.AccountManagement
public override bool ValidateUser(string username, string password) {
using (var context = new PrincipalContext(ContextType.Domain)) {
return context.ValidateCredentials(username, password);
}
}
I tried to add name and container parameters as documented on msdn to the PrincipalContext constructor, but these parameters doesn't seem to have any effect whatsoever.
using (var context = new PrincipalContext(ContextType.Domain, "MyDomain", "OU=MyCompany,DC=some,DC=stuff")) {
return context.ValidateCredentials(username, password);
}
We have the same problem on at least two different servers in our network. The servers are connected to the AD and running OS Windows server 2003 SP2 (IIS6)
One idea I had was that the issue might be connected to the fact that our domain has some trusts to other domains, and that they are somehow involved when validating the user. However, the users we are trying to validate exists exclusively in "our" AD.
Hit this issue and had to use ValidateCredentials(string, string, ContextOptions) method to pass in proper enum combination to access our ActiveDirectory connection in our environment.
Due to our clients authentication and network topology we have a number of Windows Servers in a DMZ without Active Directory or a Domain Controller. Corporate policy stipulates that passwords must change once a month. Our dev machines are in AD (not in the DMZ) so we run into the situation that we have to synchronise our usernames and passwords on each of the DMZ machines with our AD credentials every month. There are a lot of DMZ machines.
I want to use a simple console app to change the user passwords on all the DMZ machines for a given user. So far, I have the following code:
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
class Program{
static void Main(){
List<string> remoteHosts = new List<string> { "RemoteHostA", "RemoteHostB", "RemoteHostC" };
remoteHosts.ForEach(host => ChangePassword(host, "username", "oldPassword", "newPassword"));
}
static void ChangePassword(string host, string username, string oldPassword, string newPassword){
using (var context = new PrincipalContext(ContextType.Machine, host, username, newPassword))
using (var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username))
user.ChangePassword(oldPassword, newPassword);
}
}
The problem is that this only works if the password has not already changed on the dev machine where it is being run from. Because the context that is used, first has to authenticate with the dev machine in order to gain access to the network, then it has to gain access to the remote (DMZ) machine using the same context before changing the password.
How can I alter the method to use a new-password-context to gain access to the network and an old-password-context to gain access to the remote host?
Note to Title Editors: The code depends on System.DirectoryServices.AccountManagement which is an FX 3.5 assembly not 3.0.
Can you use WMI directly to achieve this? A quick search showed up this page
Note : It is a long time since I did anything with WMI, but I do remember being able to call it from a C# app, if that's how you are comfortable using it. (Check in System.Management)