Domain.GetDomain(...) fails when called from a web service - c#

I have the following code in a class that is called from a web service:
NetworkCredential credentials = new NetworkCredential("user", "password");
connection = new LdapConnection("domain");
connection.Bind(credentials);
DirectoryContext directoryContext =
new DirectoryContext(DirectoryContextType.Domain, "domain");
// This call returns a domain object with unreadable properties
Domain domain = Domain.GetDomain(directoryContext);
If I instantiate the class directly, all is well, I have a valid domain object that I can work with. If I go through the web service, the domain object is created, but most of the properties throw exceptions, e.g.:
'domain.Children' threw an exception of type ActiveDirectoryOperationException
I have impersonation enabled and am explicitly setting the credentials before calling the web service. Examining Thread.CurrentPrincipal.Identity.Name on the web service side shows the username of the credentials I've explicitly set.
If I look at Request.LogonUserIdentity, I have the following properties:
Name: "domain\\username" (is correct)
ImpersonationLevel: Impersonation
IsAnonymous: false
IsAuthenticated: true
AuthenticationType: NTLM
Anonymous access is disabled (enabling it makes no difference), and 'Basic Authentication' and 'Integrated Windows Authentication' are both checked. The web service is running under IIS 5.1 on my development box.
The code that calls the web service, resulting in a failed call to Domain.GetDomain():
MyServiceProxy proxy = new MyServiceProxy ();
CredentialCache credCache = new CredentialCache();
NetworkCredential netCred = new NetworkCredential(user, password, domain);
credCache.Add(new Uri(proxy.Url), "Ntlm", netCred);
proxy.Credentials = credCache;
proxy.MethodCall();
The code that calls directly and succeeds:
MyService myService = new MyService();
myService.MethodCall();
Any ideas why calls to Active Directory would fail when made in the context of a web service? And again, the call doesn't fail per se... it returns a domain object with unreadable properties.
Thanks in advance!

When you do...
NetworkCredential credentials = new NetworkCredential("user", "password");
connection = new LdapConnection("domain");
connection.Bind(credentials);
DirectoryContext directoryContext =
new DirectoryContext(DirectoryContextType.Domain, "domain");
// This call returns a domain object with unreadable properties
Domain domain = Domain.GetDomain(directoryContext);
...you're in fact just creating a (System.DirectoryServices.Protocols).LdapConnection with specific NetworkCredentials and you validate your "user" and "password" credentials against that. But then you don't use the connection-object anymore; instead you create a new entirely unrelated (System.DirectoryServices.ActiveDirectory).DirectoryContext-object.
And because you're not using a constructor where you're explicitly specifying a username and a password the DirectoryContext-object will get using the credentials of the user running the Application Pool (in IIS 6+, in IIS 5.1 the application will, if memory serves me right, always be a/the local system account - IUSR_XXX - which won't be able to access Active Directory because it's not a domain account).
The different credentials that are used when you're running your code in an IIS-environment versus just testing using a console application (where you're running code as the logged in/interactive user) is a common cause of problems in programming Directory Services.
Try using the constructor where you specify a username and password for the DirectoryContext-object.
As far as impersonation is concerned you might have better luck by using this code-snippet...
System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
//Insert your code that runs under the security context of the authenticating user here.
impersonationContext.Undo();
...taken from KB306158: How to implement impersonation in an ASP.NET application.

In the end, I converted this to a WCF service hosted in IIS, and the impersonation is fine. I'd love to know what the problem was, but I had to move forward, and a WCF service is a better solution overall, anyway. Thanks for the help, though!

Related

Using default credentials to call api in console app

I'm trying to call a Web API 2 method that requires auth from a console app running on my desktop where I have authorization, but I'm getting 401 Unathorized. I know I have authorization because when I make the same call from a web browser it works fine. So a browser can get my default login id/pw to send to the API but .NET's WebClient can't? That seems insane. There has to be a way to do this without entering my id/pw into the console app.
The below is what I'm using in a console app and it's not working.
This is using Windows Auth as it's intranet stuff.
This throws an exception "The remote server returned an error: (401) Unauthorized."
using(var c = new WebClient())
{
c.UseDefaultCredentials = true;
string value = c.DownloadString("http://localhost:62659/api/Store/GetData");
}
I also tried the below and when I mouse over DefaultNetworkCredentials the username/pw are blank strings. Why wouldn't .NET be able to figure this out?
using(var c = new WebClient())
{
var creds = new CredentialCache();
var uri = new Uri("http://localhost:62659/api/Store/GetData");
creds.Add(uri, "ntlm", System.Net.CredentialCache.DefaultNetworkCredentials);
c.Credentials = creds;
string value = c.DownloadString(uri);
}
It is likely that you do not have your credential information stored within Windows Credential Manager. You can access that via Control Panel > Credential Manager. From there you can add whatever credential you need. CredentialCache.DefaultCredentials and CredentialCache.DefaultNetworkCredentials contains the login credentials of the currently logged-in or impersonated user. If what you are connecting to requires different credentials then these will not work. You will need to add those credentials to the Credential Manager in Windows.
The reason you are connecting fine within Chrome is that Chrome will store credentials within itself that you have designated to save.
Login credentials being used as functional ids can be set to never expire, or it will need to be added to a list of monthly/yearly maintenance items to update the password for those accounts.
You would also want to handle bad login information within your application. If this is an automated task, have it email or otherwise notify someone that the credentials need to be updated.
If a user runs this, you could simply prompt the current user to provide a new password, which you can use to update the stored credentials right then.
Another option would be to set the user running the application as a user on the receiving end using those same credentials. That way the entire process is tied to the user(s) that will be running the application.
Using DefaultCredentials should work to use Windows Auth from console application. As long as you have the appropriate authorization header that your web api is looking for. Same with my comment I recommend testing the api call using Postman so that you can troubleshoot and check what you are missing.
Regarding the credentials as blank, this is maybe because you are using DefaultNetworkCredentials.
Try this:
using(var c = new WebClient())
{
var uri = new Uri("http://localhost:62659/api/Store/GetData");
c.Credentials = System.Net.CredentialCache.DefaultCredentials;
string value = c.DownloadString(uri);
}
If you want to use NetworkCredential you should be inputting network credentials like so:
c.Credentials = new NetworkCredential(username, password, domain);
I created this type of console application and used it as a service and I can tell you that this should work. You should just need to troubleshoot and bits by bits get the real problem.

How to store credentials in Web application to access webservice?

I am working on an application where I need to store a user credentials to access webservice to perform different operations through out user session and finally destroy the credentials.
1.User Enters credentials in Login Screen (Using Forms Authentication)
2. I am Capturing credentials and storing it in session as below:
Webservice webService = new Webservice();
CredentialCache cache = new CredentialCache();
cache.Add(new Uri(webService.Url), // Web service URL
"Negotiate", // Kerberos or NTLM
new NetworkCredential(userName, pwd, domain));
webService.Credentials = cache;
HttpContext.Current.Session["webService"] = webService;
I am not sure whether this is a correct way of doing it or not. As I need this webservice object throughout the session scope I am using above logic.
Please some one suggest me how can I accomplish this.
My application is not a SSL enabled application(Http).

Accessing SelfHosted WCF Services outside a domain

We have WCF services being self-hosted by a Windows Service inside our domain, using NetTCP with the following settings.
// Set Binding Security.
netTcpBinding.Security.Mode = SecurityMode.Transport;
netTcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
netTcpBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
We now have a requirement to allow people outside the domain to access these Services (as long as they can provide proper domain credentials). Our goal isn't to host the services via IIS, just allow those outside folks into our services. In my testing I was able to connect to a service from outside by "impersonating" the client proxy credentials during the WCF call as such.
proxy.ClientCredentials.Windows.ClientCredential.Domain = "MyDomainName";
proxy.ClientCredentials.Windows.ClientCredential.UserName = "MyUserName";
proxy.ClientCredentials.Windows.ClientCredential.Password = "MyPassword";
My question is: Is this the correct way? Is there a better way? Any advice would be greatly appreciated.
This route is perfectly valid if you need to imperatively (in code, e.g. a credential popup, or read from a configuration file) set the credentials. A more secure option is to use the windows credential cache. Firstly you would set it up to use the cache:
proxy.ChannelFactory.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
Next you would set up the credentials in the credential cache. In Windows XP/2003 this is under "Stored Usernames and Passwords," (in the control panel) in Vista/7/2008 this is under "User Account > Credential Manager" (in the control panel).
As said, your way is perfectly valid - the cache is just more secure.

Windows Form calls Web Service but needs to impersonate a different Windows user

I have a Windows Form app (4.0) that calls a Web Service (WCF) but needs to impersonate a different user than who is currently logged inot the machine. How can that be done? Right now the Web Service is failing to return records because the user does not have the rights. I need to use a different user for the Web Service call.
From my app where I do this:
NetworkCredential credentials = new NetworkCredential(user, pw, userDomain);
// This is the client generated by the WCF Service Reference
AppClient appClient = new AppClient();
appClient.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
appClient.ClientCredentials.Windows.ClientCredential = credentials;
appClient.MyWcfServiceCall();
Now calls to the WCF service will be done under the credentials supplied. Your WCF Service methods must be decorated to allow impersonation as such:
[OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
or
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
depending on your needs.
From within the WCF Service, you have the following info about the logged on user:
OperationContext.Current.ServiceSecurityContext
Thread.CurrentPrincipal.IsInRole(roleName)
Thread.CurrentPrincipal.Identity
You can also look into LogonUser() for other methods of impersonation: http://msdn.microsoft.com/en-us/library/ff647404.aspx#paght000023_impersonatingusinglogonuser
HTH!
James

WCF windows credentials

my client on server A calls a service on B which calls a service on C.
In order to get the call working from B->C I have to do this:
channel.Credentials.Windows.ClientCredential =
new System.Net.NetworkCredential("WndowsUserName", "WindowsPassWord");
IService1 service = channel.CreateChannel();
etc...
the user name and password are the windows credentials used from A->B
Of course I do not want to hardcode this so how can I do this without hardcoding?
I tried, with no luck:
WindowsIdentity callerWindowsIdentity =
ServiceSecurityContext.Current.WindowsIdentity;
using (callerWindowsIdentity.Impersonate())
Use
System.Net.CredentialCache.DefaultNetworkCredentials
property. It represents the authentication credentials for the current security context in which the application is running. Details can be found here.
It seems to be a "double hop" authentication problem.
In short, NTLM doesn't alllow more than one "hop" with it's credentials (token). So user authenticates on server 1 with it's token, and in turn, server 1 tries to send the token to server 2. This won't work, unless Kerberos deleguation is allowed between server 1 and 2.
More details here : http://weblogs.asp.net/owscott/archive/2008/08/22/iis-windows-authentication-and-the-double-hop-issue.aspx
And here : http://blogs.msdn.com/nunos/archive/2004/03/12/88468.aspx
Perhaps the class
System.Net.CredentialCache
could be helpfull ...
It has the DefaultCredentials and DefaultNetworkCredentials properties that you can use.
Offcourse, you will have to make sure that your application runs under the credentials that you want (that is , the credentials of the current user).
This can be done by calling
AppDomain.CurrentDomain.SetPrincipalPolicy (PrincipalPolicy.WindowsPrincipal);
At the start of your program.
Then, when you initialize the WCF service, you can use the DefaultNetworkCredentials provided by the CredentialCache.
channel.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
IService1 service = channel.CreateChannel();

Categories