Active directory access and IIS Authetication method issue - c#

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.

Related

Client/Server app, how to create process on remote system as a domain user without transferring that users username/password to the remote system?

I have two systems both running my C# client/server software code. I want to from Computer 1 create a process as a given Active Directory domain user on Computer 2 without having to have my client/server software send the plain text username and password of the AD user from Computer 1 to Computer 2.
The closest I have gotten is it seemed like I could use the function KerberosRequestorSecurityToken on Computer 1 to generate a kerberos ticket and then send that byte[] result via my client/server code to Computer 2 where it should then be able to call KerberosReceiverSecurityToken against the byte[] kerberos ticket that was passed. If all that works then KerberosReceiverSecurityToken would have a WindowsIdentity property that I could then use to create a process on Computer 2 with the user account initially specified on Computer 1 via the KerberosRequestorSecurityToken flow.
I need to use existing Domain User accounts that I want to impersonate vs. registering a service account SPN. This is the crux of the issue that I failed to mention when I originally posted the question.
I cannot seem to get this to work but more so I do not even know if this is actually possible - e.g. should these APIs even allow me to do what I want?
Computer 1 code to generate kerberos ticket. The byte[] result of this is sent via my client/server app to Computer 2.
public static byte[] GetRequestToken(string userName, string password, string domain)
{
using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
{
using (var foundUser = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName))
{
Console.WriteLine("User Principal name" + UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName).UserPrincipalName);
string spn = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userName).UserPrincipalName;
KerberosSecurityTokenProvider k1 = new KerberosSecurityTokenProvider(spn, TokenImpersonationLevel.Impersonation, new NetworkCredential(userName, password, domain));
KerberosRequestorSecurityToken T1 = k1.GetToken(TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;
var req = T1.GetRequest();
return req;
}
}
}
Computer 2 takes that resulting byte[] kerberos ticket and attempts to recieve it to access a WindowsIdentity but it throws an exception saying invalid logon.
KerberosReceiverSecurityToken receiverToken = new KerberosReceiverSecurityToken(requestToken);
byte[] receiverTokenTicket = receiverToken.GetRequest();
//Identity for impersonation
var impersonatedIdentity = receiverToken.WindowsIdentity;
The origin of the first block of code is functionally wrong. The SPN must be the target receiving the ticket, not the user requesting the ticket. So you need to do the following things...
Register a service principal account (user/computer) in AD.
Add a service principal name to that account of the form foo/host.domain.com where foo is your service name, e.g. http or host, or myapp.
Client needs to request a ticket to foo/host.domain.com
KerberosReceiverSecurityToken should parse that ticket
Now you have an identity of some varying impersonation level. That impersonation level dictates whether you can start a process as that user. As it happens... you just can't because that's not how Windows security works. You have an impersonation NT token since you're impersonating a user, whereas you need a primary NT token to start processes.
So you need to convert that to a primary token using DuplicateTokenEx, and you effectively need to be SYSTEM to do that.
Once you have the primary token you can call CreateProcessAsUser.
At this point you might have started the process as the user, but it doesn't have any credentials to reach other network properties like network shares or web services. You can enable constrained delegation on the service principal to any downstream services for that.
An alternative, and probably safer and easier, option is instead to start a worker process as a low privilege user and tell that process to impersonate. See this answer for more information on that.

C# Remove AD Computer Account Running on Windows Service

I want to write a function in a windows service application to remove a given computer name from Active Directory.
The Windows service is running on a machine which is domain-joined to the DC. Currently I have logged in to this machine with domain admin account.
The Windows service is running under the security context of "NT AUTHORITY/SYSTEM" and this should not be changed, as there shouldn't be any user interaction after installing the application, meaning that admin shouldn't enter their credentials in services.
When I run the application with my newly added code to delete the computer account, it doesn't work. However, when I change the logon info on the Windows Service and update that with domain admin credentials, it's able to successfully remove the computer account from AD.
Below is [a shortened version of] the function used to delete computer accounts.
Is there any way I can modify the code to be able to remove Computer Accounts using the same security context (NT AUTHORITY/SYSTEM)?
private void DeleteComputerAccount(string CompName, DirectoryEntry DirEntry)
{
try
{
//Delete computer account
DirectorySearcher search = new DirectorySearcher(DirEntry, "(name=" + CompName + ")");
SearchResult res = search.FindOne();
res.GetDirectoryEntry().DeleteTree();
}
catch (Exception)
{
Throw();
}
}
Where DeleteComputerAccount is called:
DirectoryEntry dirEntry = new DirectoryEntry("LDAP://domain.contoso.com");
string compName = "MyWorkstation01";
DeleteComputerAccount(compName, dirEntry);
When a service runs as local system, it will access the network (and thus AD) in the security context of the host's computer account. You can delegate the computer account (or better, a group which the computer is a member of) the ability to delete objects from AD. This link has accurate advice on how to complete that task - http://sigkillit.com/2013/06/12/delegate-adddelete-computer-objects-in-ad/
While not what you asked, a couple other things stand out to me:
You're not filtering your search very well. You might get something other than what you want back. I'd suggest a filter like this instead: (&(objectClass=computer)(sAMAccountName=<PCName>$))
Local system is a lot of access. Could you run this as network service instead?

How to run application in an authenticated manner

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.

login with active directory autentication in asp.net c#

I am creating the asp.net web application. In that I used active directory for login. I have one group named Validusers, when user logged in it checks the user to that validusers group. If the user exists in the group then login is successful, if not login failed.
I did all the things, it works good in my local machine, but it is not working when I publishing the website. I got the following error
Logon failure: unknown user name or bad password.
On my machine it works good,while publishing i got an error.i used below code for your reference
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DomainName");
System.Security.Principal.WindowsIdentity MyIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
string LogUser = MyIdentity.Name.ToString();
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, LogUser);
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "Validusers");Validusers---->Groupname
if (user.IsMemberOf(group))
{
Login success
}
else
{
Login Failed
}
it will check logged user with Validusers group.if the user exists in the group then login is success other wise failed login. I got an error when i published this website.please give some solution
Although you had supplied so little information about the domain and network relationship with your development machine and web server, I assume the web server has no physical connection to the Active Directory server that you depend your code on. Then it should be impossible for web server to query the AD directory.
If web and AD servers are on same network than you may need to work on the firewall settings of both web server and AD server to make sure that they can communicate.
If web and AD server have no communication problems you should check the availability of that "Validusers" to a code running at web server.
As IIS applications run with the user account that is defined for the application pool that hosts the application, you should make sure that the app pool identity has enough rights to access those delicate information.

How does PrincipalContext login to domain server

I have the following C# code that connects to my domain server and performs some actions on it. Everything works fine on my computer and I can run all my commands just fine.
My questions is: what credentials are used for the connection to the server? I assume it uses the current users credentials. So my real question is will this work on a normal user. I am an admin and it works fine on my machine.
However I am wondering if the same would hold true for a non admin?
PrincipalContext AD = new PrincipalContext(ContextType.Domain, "172.18.4.4");
UserPrincipal u = new UserPrincipal(AD);
u.SamAccountName = Environment.UserName;
PrincipalSearcher search = new PrincipalSearcher(u);
UserPrincipal result = (UserPrincipal)search.FindOne();
If that code is running in a Windows app then the credentials used are the ones of the current Windows user and it should work fine in a domain. If the code is running in an ASP.NET site, then the credentials are the ones of the application pool in which the site is running. In this last case, you might need to play with the Identity of the app pool: LocalSystem, NetworkService...
In any of the previous cases, you can impersonate a user to run this code under: (look at the answer)
How to use LogonUser properly to impersonate domain user from workgroup client

Categories