i have some code that tries impersonate the callers windows security settings and then connect to another WCF service on a different machine
WindowsIdentity callerWindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity;
using (callerWindowsIdentity.Impersonate())
{
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
EndpointAddress endpoint = new EndpointAddress(new Uri("net.tcp://serverName:9990/TestService1"));
ChannelFactory<WCFTest.ConsoleHost.IService1> channel = new ChannelFactory<WCFTest.ConsoleHost.IService1>(binding, endpoint);
WCFTest.ConsoleHost.IService1 service = channel.CreateChannel();
return service.PrintMessage(msg);
}
But I get the error:
"the caller was not authenticated by the service"
System.ServiceModel .... The request for security token could not be satisfied because authentication failed ...
The credentials I am trying to impersonate are valide windows credential for the box the service is on.
Any ideas why?
In order to support your scenario, you need to have an understanding of how Protocol Transition and Constrained Delegation work. You will need to configure both Active Directory and your WCF service endpoint(s) to support this. Note the use of the Service Principal Name (SPN). Take a look at the following link and see if they help you. The article has a sample to demonstrate the complete end-to-end configuration required to make this work.
How To: Impersonate the Original Caller in WCF Calling from a Web Application
Agree with marc_s this is the double-hop problem.
You need to get the windows authentication all the way through, therefore:
The request must be made in the context of a windows users
IIS must be configured to use windows authentication
Web.config must be set up for windows authentication with impersonate = true
The user that your application pool is running as, must be allowed to impersonate a user. This is the usual place where the double-hop problem occurs.
There is a right called "Impersonate a client after authentication"
http://blogs.technet.com/askperf/archive/2007/10/16/wmi-troubleshooting-impersonation-rights.aspx
Impersonation from you service to the next is a tricky issue, known as "double-hop" issue.
I don't have a final answer for that (I typically avoid it by using an explicit service account for the service that needs to call another service).
BUT: you should definitely check out the WCF Security Guidance on CodePlex and search for "Impersonation" - there are quite a few articles there that explain all the ins and outs of impersonating an original caller and why it's tricky.
Marc
If you are sure you have the credentials right on both hops, the next thing that could be causing the issue is the lack of the EndpointDnsIdentity being set on the endpoint.
DnsEndpointIdentity identity = new DnsEndpointIdentity("localhost"); // localhost is default. Change if your service uses a different value in the service's config.
Uri uri = new Uri("net.tcp://serverName:9990/TestService1");
endpoint = new EndpointAddress(uri, identity, new AddressHeaderCollection());
Related
I'm having some issues implementing a client that talks to a WCF service. It's a WCF hosted by another company so I don't have access to its code. I used the Connected Service provider tool in Visual Studio to generate the client code so that I could make requests and everything works fine on my local machine. I am having an issue on our development environment where I receive the following error if I try to make a request with the client:
The HTTP request was forbidden with client authentication scheme 'Anonymous'
I've been looking at the client code (it's a lot of code) which is generated by the Provider tool and I think it may have something to do with the following block of code.
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
return result;
This more linked to firewall rules within corporate network.
I had same issue using non authorized proxy but got resolved secured proxy with ntlm ClientCredentialType
This error typically indicates that the WCF server authenticates the client-side with a certificate. The error will occur when the trust relationship between the server and the client have not been established yet.
In general, we need to provide client credential to be authenticated by the server so that be able to call the service. As for what kind of credentials need to be provided, it depends on the binding information on the server-side.
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
Namely, the above errors have indicated that the server authenticates the client with a certificate.
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
About authenticating the client with a certificate, you could refer to the below link for details.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication
Feel free to let me know if there is anything I can help with.
Thanks for all the suggestions. This was actually just caused by a firewall rule that was setup within our organisation. Once that was removed the code worked as expected.
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
Security is provided using HTTPS. The service must be configured with SSL certificates. The SOAP message is protected as a whole using HTTPS. The service is authenticated by the client using the service's SSL certificate. The client authentication is controlled through the ClientCredentialType.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.basichttpsecuritymode?view=netframework-4.8
I have two sites, A and B. A consumes an API that B exposes, and B requires Windows authentication. Both sites live in Domain D.
The API is consumed via HttpClient, and when site A is run locally, under my domain account (which is in Domain P), access is granted. In this case, HttpClient is instantiated like so:
using(var client = new HttpClient(new HttpClientHandler { UseDefaultCredentials: true }))
When A is deployed to a testing server, the above results in a 401 Unauthorized response. The application pool on the testing server is running under a service account in domain D.
When explicitly using that service account like this:
var credential = new NetworkCredential("service-account", "password", "D");
var cache = new CredentialCache
{
{
new Uri(apiServerUri), "NTLM", credential
}
};
var handler = new HttpClientHandler
{
Credentials = cache
};
using(var client = new HttpClient(handler))
...
And again running site A locally, access is still granted. Access is also granted when accessing the API directly via browser, and specifying the service account credentials. Logs indicate that it is definitely the service account being used to access the API.
Deploying the above back to the testing server still results in 401 Unauthorized.
Deploying site A to a local instance of IIS, also successfully consumes the API of B.
Running site B locally, and then accessing it via site A locally, results in a 401 Unauthorized.
Accessing the API through a browser on the testing server where A is deployed, and specifying the service account credentials, also gives a 401 Unauthorized.
I'm not sure where to go from here - am I missing something in the code to get this working? Or is it likely to be an IIS or AD issue?
While I'm yet to determine exactly why this work around works, or if there is a better way of doing it (because this feels clunky), the following has allowed A to connect to B, when both are sitting on the same server.
Site B has had an additional host binding setup in IIS, to listen on localhost:12345. Site A has been configured to connect to that endpoint, rather than the domain name for Site B. Authentication is now working correctly.
I would be interested if anyone can explain why this is the case - I dislike 'magic' fixes.
edit
It would seem that this kb article is a likely cause for this behavior. Specifically:
When you use the fully qualified domain name (FQDN) or a custom host
header to browse a local Web site that is hosted on a computer that is
running Microsoft Internet Information Services (IIS) 5.1 or a later
version, you may receive an error message that resembles the
following: HTTP 401.1 - Unauthorized: Logon Failed This issue occurs
when the Web site uses Integrated Authentication and has a name that
is mapped to the local loopback address
and
Therefore, authentication fails if the FQDN or the custom host header that you use does not match the local computer name.
Registry modifications aren't really an option on these servers, so looks like the work around is what we will be using.
I have a WCF service which is running fine.
It is used within an intranet network.
It is a self-hosted service
(no IIS) managed by a simple Windows Form program.
It is used by a
WCF client (WPF C#).
I now need to add security to it and after having read a lot of posts on the internet I'm getting confused as there are many ways of doing.
I need a custom username and password validator (I will have to call another web service to know if user is authorized or not).
I also need secure communication between client and server.
I am currently using basicHttpBinding.
MS recommends the use of NetTcpBinding in my case (https://msdn.microsoft.com/en-us/library/ff648863.aspx#TransportSecurityWCF), but I am not sure if this is or can be secured ?
I think I better use WsHttpBinding to have SSL: do you think that this link provides proper solution to my case ? https://msdn.microsoft.com/en-us/library/ms733775.aspx ?
Thanks for your advices
You can do SSL/Transport encryption with BasicHTTPBinding. That doesn't need to change; you just need to set up the host side with "Transport" security, add some code and a certificate, and you should be able to proceed without changing too much code. I can include a small code sample below, since I did the same thing you're trying to do via a self-hosted service.
BasicHttpBinding b = default(BasicHttpBinding);
if (bUseSSL) {
//check for ssl msg credential bypass
if (bSSLMsgCredentialBypass) {
b = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
} else {
b = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
}
b.TransferMode = TransferMode.Buffered;
b.MaxReceivedMessageSize = int.MaxValue;
b.MessageEncoding = WSMessageEncoding.Text;
b.TextEncoding = System.Text.Encoding.UTF8;
b.BypassProxyOnLocal = false;
//b.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;
}
The authentication/authorization can be done, too, without changing what you currently have. You really have two choices:
One is that you create a Login function that get's called when the client first visits the host. You then send some token value back to the client for all subsequent communications.
The other way involves creating that custom authentication check, using the message inspector functionality found in Dispatcher.IDispatchMessageInspector and a public function called AfterReceiveRequest. Within that function, you can examine the UserID and Pwd (from within the HTTP header data) sent from the clients- but you need to implement this on both the client and host sides, otherwise it doesn't work.
I am hosting a soap webservice via an instance iHost of ServiceHost; authentication is configured as
HttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
iHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode
= UserNamePasswordValidationMode.Custom;
iHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator
= new CustomValidator();
The hosting itself works as desired, however I also would like to access the client credentials from within the hosted service itself. Can this be achieved with the current authentication settings or is it impossible?
Found the answer with the help of a coworker. Username can be accessed via OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name; the question can be seen as a duplicate of this question.
I am struggling to understand and set up a Service and Consumer where the Service will run as the user logged into the Consumer.
My consumer is an MVC application. My Service is a Web Api application. Both run on separate servers within the same domain. Both are set to use Windows Auth.
My consumer code is:
private T GenericGet<T>(string p)
{
T result = default(T);
HttpClientHandler handler = new HttpClientHandler() { PreAuthenticate = true, UseDefaultCredentials = true };
using (HttpClient client = new HttpClient(handler))
{
client.BaseAddress = new Uri(serviceEndPoint);
HttpResponseMessage response = client.GetAsync(p).Result;
if (response.IsSuccessStatusCode)
result = response.Content.ReadAsAsync<T>().Result;
}
return result;
}
In my Service I call User.Identity.Name to get the caller ID but this always comes back as the consumer App Pool ID, not the logged in user. The consumer App Pool is running as a Network Service, the server itself is trusted for delegation. So how do I get the logged in User? Service code:
// GET: /Modules/5/Permissions/
[Authorize]
public ModulePermissionsDTO Get(int ModuleID)
{
Module module= moduleRepository.Find(ModuleID);
if (module== null)
throw new HttpResponseException(HttpStatusCode.NotFound);
// This just shows as the App Pool the MVC consumer is running as (Network Service).
IPrincipal loggedInUser = User;
// Do I need to do something with this instead?
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
ModulePermissionsDTO dto = new ModulePermissionsDTO();
// Construct object here based on User...
return dto;
}
According to this question, Kerberos is required to make this set up work because the HttpClient runs in a separate thread. However this confuses me because I thought the request sends an Authorization header and so the service should be able to use this and retrieve the user token. Anyway, I have done some testing with Kerberos to check that this correctly works on my domain using the demo in "Situation 5" here and this works but my two applications still wont correctly pass the logged in user across.
So what do I need to do to make this work? Is Kerberos needed or do I need to do something in my Service to unpack the Authorisation header and create a principal object from the token? All advice appreciated.
The key is to let your MVC application (consumer) impersonate the calling user and then issue the HTTP requests synchronously (i.e. without spawning a new thread). You should not have to concern yourself with low-level implementation details, such as NTLM vs Kerberos.
Consumer
Configure your MVC application like so:
Start IIS Manager
Select your MVC web application
Double click on 'Authentication'
Enable 'ASP.NET Impersonation'
Enable 'Windows Authentication'
Disable other forms of authentication (unless perhaps Digest if you need it)
Open the Web.config file in the root of your MVC application and ensure that <authentication mode="Windows" />
To issue the HTTP request, I recommend you use the excellent RestSharp library. Example:
var client = new RestClient("<your base url here>");
client.Authenticator = new NtlmAuthenticator();
var request = new RestRequest("Modules/5/Permissions", Method.GET);
var response = client.Execute<ModulePermissionsDTO>(request);
Service
Configure your Web API service like so:
Start IIS Manager
Select your Web API service
Double click on 'Authentication'
Disable 'ASP.NET Impersonation'.
Enable 'Windows Authentication'
If only a subset of your Web API methods requires users to be authenticated, leave 'Anonymous Authentication' enabled.
Open the Web.config file in the root of your Web API service and ensure that <authentication mode="Windows" />
I can see that you've already decorated your method with a [Authorize] attribute which should trigger an authentication challenge (HTTP 401) when the method is accessed. Now you should be able to access the identity of your end user through the User.Identity property of your ApiController class.
The key issue with double hop is delegation of user credential to second call. I want to elaborate a little bit about it. C1 = client browser , S1 = First Server , S2 = Second Server.
Suppose our complete system support window authentication. When user access S1 from browser , its default window credential pass to server S1, but when S1 make a call to S2 , by default it don't pass credential to S2.
Resolution :
We must enable window authentication/ impersonation on both machines.
WE need to enable delegation between server so that S1 can trust to S2 and will pass credential to S2.
You can find some useful details at below links :
http://blogs.msdn.com/b/farukcelik/archive/2008/01/02/how-to-set-up-a-kerberos-authentication-scenario-with-sql-server-linked-servers.aspx
https://sqlbadboy.wordpress.com/2013/10/11/the-kerberos-double-hop-problem/
If you are trying to access service which is hosted on windows authentication then do following.
var request = new RestRequest(Method.POST);
If you want to use applications default credentials which must have access on hosted service server
request.UseDefaultCredentials = true;
or user below to pass the credentials manually
request.Credentials = new NetworkCredential("Username", "Password", "Domain");