I have a WCF Service being hosted by a windows service. The client app succeeds if I log on to the client machine using the same credentials as the service is running under, but it fails with an exception if I am logged in with any other valid domain account.
I have two accounts that I am testing with, one is an ordinary user account and the other account is an admin account. I have tried all four combinations listed below:
Server account
CLient RegUser AdminAcct
RegUser Succeeds Fails
AdminAcct Fails Succeeds
As you can see it cannot be an admin issue as the system works when both client and server are running under the non-admin account. In both cases where it fails I get the same exception, on the client, with no indication of anything happening in the server logs:
"A call to SSPI failed. see inner exception"
The inner exception is "The target principle name is incorrect."
I have registered the accounts as SPNs.
The problem only occurs from my client app, but not when I use the WCVFTestClient.exe
which ships with Visual Studio.
The exception, in the WCF Trace log, is
"System.ServiceModel.Security.SecurityNegotiationException, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
with a message:
"Authentication failed on the remote side (the stream might still be available for additional authentication attempts)."
The stack trace is at the bottom: What is wrong ?
stack trace
System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeAcceptor.OnAcceptUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity) System.ServiceModel.Channels.StreamSecurityUpgradeAcceptorBase.AcceptUpgrade(Stream stream) System.ServiceModel.Channels.InitialServerConnectionReader.UpgradeConnection(IConnection connection, StreamUpgradeAcceptor upgradeAcceptor, IDefaultCommunicationTimeouts defaultTimeouts) System.ServiceModel.Channels.ServerSessionPreambleConnectionReader.ServerFramingDuplexSessionChannel.OnOpen(TimeSpan timeout) System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) System.ServiceModel.Dispatcher.ChannelHandler.OpenAndEnsurePump() System.Runtime.ActionItem.DefaultActionItem.TraceAndInvoke() System.Runtime.ActionItem.CallbackHelper.InvokeWithoutContext(Object state) System.Runtime.IOThreadScheduler.ScheduledOverlapped.IOCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped) System.Runtime.Fx.IOCompletionThunk.UnhandledExceptionFrame(UInt32 error, UInt32 bytesRead, NativeOverlapped* nativeOverlapped) System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
Found the answer. My problem was a combination of two factors.
When using the net.tcp binary WCF protocol, the Client Security Mode determines whether NTLM or Kerberos is used for authentication. If you set Client Security Mode to "Transport", Authentication uses NTLM and only one hop is possible. If you try to have the WCF Server talk to a third server, (like a database), it will fail. Using SecurityMode = "Message", otoh, causes the WCF server to use Kerberos, which allows multiple hops...
The second issue was related to what I was doing on the client in the binding. WCF protocol net.tcp requires that when instantiating the endpoint on the client, you must specify an "endpoint identity" (see code below) . I had wrongly assumed this was somehow related to authentication, and was, therefore, the identity of the currently logged on user (Windows Principal) on the client.
var epId = EndpointIdentity.CreateUpnIdentity(userPrincipalName);
var ep = new EndpointAddress(new Uri(url), epId):
No... The identity which must be specified in the creation of the endpoint on the client must be the identity the server is running under. This is why the code worked whenever I was logged on to the client with the same user as the service was running under, and failed when the client was a different user.
I still do not understand why this user identity (of the service account) must be specified in the endpoint on the client. What function on the server is this data needed for?
Regarding the second point, the client is not required to run under the same Windows account of the server in order to be authenticated successfully, and it's not required to manually specify the account name in the request. From what I understand, WCF authentication is mutual, meaning the client is verifying the server as well.
The error you received is probably due to failure on the client side. If you configure the service endpoint of the client to provide a ServicePrincipalName, the problem might well be resolved. I found this article pretty helpful to solve the same issue I encountered.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With