C# – Windows Authentication using WCF and Self Hosting

authenticationcwcf

I've developed a very simple host and client which I wanted to use to test whether it would be possible for a WCF client to pass the logged on windows' user's credentials to the host service without requiring the user to re-enter their credentials or setup security.

My host config looks like this:

<configuration>
  <system.serviceModel>
    <services>
      <service name="WCFTest.CalculatorService" behaviorConfiguration="WCFTest.CalculatorBehavior">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8000/WCFTest/CalculatorService/" />
            <add baseAddress = "net.tcp://localhost:9000/WCFTest/CalculatorService/" />
          </baseAddresses>
        </host>
        <endpoint address ="basicHttpEP" binding="basicHttpBinding" contract="WCFTest.ICalculatorService" bindingConfiguration="basicHttpBindingConfig"/>
        <endpoint address ="netTcpEP" binding="netTcpBinding" contract="WCFTest.ICalculatorService"/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>

      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="basicHttpBindingConfig">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WCFTest.CalculatorBehavior">          
          <serviceAuthorization impersonateCallerForAllOperations="false"  principalPermissionMode="UseWindowsGroups" />
          <serviceCredentials >
            <windowsAuthentication allowAnonymousLogons="false" includeWindowsGroups="true" />
          </serviceCredentials>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

My client config looks like this:

<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="WCFClient.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_ICalculatorService" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                    useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="TransportCredentialOnly">
                        <transport clientCredentialType="Windows" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
            <netTcpBinding>
                <binding name="NetTcpBinding_ICalculatorService" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
                    hostNameComparisonMode="StrongWildcard" listenBacklog="10"
                    maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"
                    maxReceivedMessageSize="65536">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <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="http://ldndwm286380:8000/WCFTest/CalculatorService/basicHttpEP"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICalculatorService"
                contract="CalcService.ICalculatorService" name="BasicHttpBinding_ICalculatorService" />
            <endpoint address="net.tcp://ldndwm286380:9000/WCFTest/CalculatorService/netTcpEP"
                binding="netTcpBinding" bindingConfiguration="NetTcpBinding_ICalculatorService"
                contract="CalcService.ICalculatorService" name="NetTcpBinding_ICalculatorService">
            </endpoint>
        </client>
    </system.serviceModel>
    <userSettings>
        <WCFClient.Settings1>
            <setting name="Setting" serializeAs="String">
                <value>True</value>
            </setting>
        </WCFClient.Settings1>
    </userSettings>
</configuration>

My client code is:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Press enter to start");
        Console.ReadLine();

        CalcService.ICalculatorService httpCalcService = new CalcService.CalculatorServiceClient("BasicHttpBinding_ICalculatorService");
        Console.WriteLine(httpCalcService.AddValues(new int[] { 1, 2, 3 }).Value);
        Console.ReadLine();

        CalcService.ICalculatorService TcpCalcService = new CalcService.CalculatorServiceClient("NetTcpBinding_ICalculatorService");
        Console.WriteLine(httpCalcService.AddValues(new int[] { 5, 10, 15 }).Value);
        Console.ReadLine();
    }
}

This works fine if I run the client on my PC. If I run the client on a colleague's PC I get this exception stack on the client:

Unhandled Exception:
System.ServiceModel.Security.MessageSecurityException:
The HTTP request is unauthorized with
client authentication scheme
'Negotiate'. The authentication header
received from the server was
'Negotiate oX0we6ADCgEBonQEcm
BwBgkqhkiG9xIBAgIDAH5hMF+gAwIBBaEDAgEepBEYDzIwMTAwMzExMTAzNjAzWqUFAgMEDFimAwIBKakYGxZJTlRSQU5FVC5CQVJDQVBJTlQuQ09NqhowGK
ADAgEBoREwDxsNTERORFdNMjg2MzgwJA=='.
—> System.Net.WebException: The remote server returned an error: (401)
Unauthoriz ed. —>
System.ComponentModel.Win32Exception:
The target principal name is incorrect
at
System.Net.NTAuthentication.GetOutgoingBlob(Byte[]
incomingBlob, Boolean throwOnError,
SecurityStatus& statusCode)

at
System.Net.NTAuthentication.GetOutgoingBlob(String
incomingBlob) at
System.Net.NegotiateClient.DoAuthenticate(String
challenge, WebRequest webRequest,
ICredentials credentials, Boole an
preAuthenticate) at
System.Net.NegotiateClient.Authenticate(String
challenge, WebRequest webRequest,
ICredentials credentials) at
System.Net.AuthenticationManager.Authenticate(String
challenge, WebRequest request,
ICredentials credentials) at
System.Net.AuthenticationState.AttemptAuthenticate(HttpWebRequest
httpWebRequest, ICredentials authInfo)
at
System.Net.HttpWebRequest.CheckResubmitForAuth()
at
System.Net.HttpWebRequest.CheckResubmit(Exception&
e) — End of inner exception stack
trace — at
System.Net.HttpWebRequest.GetResponse()
at
System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan
timeou t) — End of inner
exception stack trace —

Any help appreciated,

Thanks,

Nick

Best Answer

Within the configuration for your client, you'll need to add an additional element within your 'endpoint' element to specify the principal that the service is running under. Here is an example:

<identity>
      <servicePrincipalName value="user@mydomain.com" />
</identity>

This should allow for proper authentication to occur when establishing a channel between your client and host. If the service is being run under a different account, you will need to adjust the value accordingly (i.e. if it is running as a windows service using the system account, it would be similar to 'host/mymachine.mydomain.com').

Related Topic