Our project uses WCF services hosted on multiple machines. The binding used can be chosen from predefined options. One of the options is NetTcpBinding.
A website hosted in IIS with anonymous authentication is used to consume the WCF service. When the service is hosted on the same machine everything works, but when some other machine hosts the service (no domain) an exception is thrown
<Exception> <ExceptionType> System.IdentityModel.Tokens.SecurityTokenValidationException, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 </ExceptionType> <Message> The service does not allow you to log on anonymously. </Message> <StackTrace> ... </StackTrace> <ExceptionString> ... </ExceptionString> </Exception>
Repro
For investigating the issue we created a small service and a website that calls the service from a button click. The interesting parts of the code is shown below.
# Create a selfsigned certificate from the command prompt. makecert -sky exchange -r -n CN=other_machine -pe -a sha1 -len 2048 -ss My -sr LocalMachine c:\other_machine.cer
The other_machine.cer should be imported in Trusted People on all machines that call the WCF service.
// Main of the console app that hosts the calculate service var machine = System.Net.Dns.GetHostName(); var address = string.Format("net.tcp://{0}:8000/calc.svc", machine); var binding = new NetTcpBinding(SecurityMode.Transport); var contract = typeof(ICalculate); using (var host = new ServiceHost(typeof(CalculateService))) { host.Credentials.ServiceCertificate.Certificate = GetCertificateByThumbprint("thumbprint_here"); var endpoint = host.AddServiceEndpoint(contract, binding, address); host.Open(); Console.WriteLine("Listening on {0}\nPress <CR> to stop.", address); Console.ReadLine(); host.Close(); }
Host the service in the console app and configure tracing to capture the exception.
// btn1_click code var binding = new NetTcpBinding(SecurityMode.Transport); var address = "net.tcp://other_machine:8000/calc.svc"; using (var proxy = new NetTcpService.CalculateProxy(binding, address)) { var r = new Random(); var a = r.Next(1, 199); var b = r.Next(1, 199); var result = proxy.Add(a, b); btn1.Text = string.Format("{0} + {1} = {2}", a, b, result); }
The website is hosted with all the defaults: DefaultAppPool, Anonymous Authentication.
After clicking the button the exception was logged to the trace file. We’ve got a repro.
Investigation
Internet is full of solutions. We’ve tried setting the credentials of the website, change application pool and allowed anonymous logons on windowsauthentication credentials. This last “solution” triggered use to look into the defaults of the NetTcpBinding. Seems the default of the clientCredentialType is Windows.
Solution
We’re not interested in the windows credentials and do want to allow anonymous access to our calculate service. The solution in the repro code above is to set the clientCredentialType to none.
var binding = new NetTcpBinding(SecurityMode.Transport); // add this to the console app and the btn1_click binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
Now we can access the webservice from all clients including a website with anonymous authentication.
References
- Configure tracing article on MSDN
- Transport element of netTcpBinding article on MSDN