WCF Net.tcp with sspi fails unless client and server using same windows Identity

Charles Bretana picture Charles Bretana · Mar 25, 2013 · Viewed 16.1k times · Source

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)

Answer

Charles Bretana picture Charles Bretana · Mar 31, 2013

Found the answer. My problem was a combination of two factors.

  1. 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...

  2. 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?