I'm having a strange situation here. I got it working, but I don't understand why. Situation is as follows:
There is a WCF service which my application (a website) has to call. The WCF service exposes a netTcpBinding and requires Transport Security (Windows).
Client and server are in the same domain, but on different servers.
So generating a client results in the following config (mostly defaults)
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="MyTcpEndpoint" ...>
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Transport">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
<message clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:xxxxx/xxxx/xxx/1.0"
binding="netTcpBinding" bindingConfiguration="MyTcpEndpoint"
contract="Service.IMyService" name="TcpEndpoint"/>
</client>
</system.serviceModel>
When I run the website and make the call to the service, I get the following error:
System.ServiceModel.Security.SecurityNegotiationException: Either the target name is incorrect or the server has rejected the client credentials. ---> System.Security.Authentication.InvalidCredentialException: Either the target name is incorrect or the server has rejected the client credentials. ---> System.ComponentModel.Win32Exception: The logon attempt failed
--- End of inner exception stack trace ---
at System.Net.Security.NegoState.EndProcessAuthentication(IAsyncResult result)
at System.Net.Security.NegotiateStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
at System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.InitiateUpgradeAsyncResult.OnCompleteAuthenticateAsClient(IAsyncResult result)
at System.ServiceModel.Channels.StreamSecurityUpgradeInitiatorAsyncResult.CompleteAuthenticateAsClient(IAsyncResult result)
--- End of inner exception stack trace ---
Server stack trace:
at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
....
Now, if I just alter the configuration of the client like so:
<endpoint address="net.tcp://localhost:xxxxx/xxxx/xxx/1.0"
binding="netTcpBinding" bindingConfiguration="MyTcpEndpoint"
contract="Service.IMyService" name="TcpEndpoint">
<identity>
<dns />
</identity>
</endpoint>
everything works and my server happily reports that it got called by the service account which hosts the AppPool for my website. All good.
My question now is: why does this work? What does this do? I got to this solution by mere trial-and-error. To me it seems that all the <dns />
tag does is tell the client to use the default DNS for authentication, but doesn't it do that anyway?
UPDATE
So after some more research and trial-and-error, I still haven't found an answer to this problem.
In some cases, if I don't provide the <dns />
, I get the Credentials rejected
error, but if I provide the <dns value="whatever"/>
config, it works. Why?
Not an answer, but the same "trick" works if you create the EndpointAddress via code:
It's strange and I don't know why this is working, but it seems to help.
<dns/>
tag allows the client to verify the server identity. For example if you said<dns value="google.com"/>
it would verify that the WCF server provides google.com identity. Since you say<dns/>
it probably just allows everybody to serve you.More info at Service Identity and Authentication
MSDN's "Service Identity and Authentication" explains that the endpoint identity section enables a client-side security precaution against phishing schemes.
From MSDN:
Also see MSDN's "Service Identity Sample".