Monday, 2 July 2012

WCF and Windows Authentication - The HTTP request is unauthorized with client authentication scheme 'Negotiate'

I have a simple WCF service hosted with in Console application, secured by Windows Authentication.

    <services>
      <service name="MyService.Service1" behaviorConfiguration="MyServiceTypeBehaviors" >
        <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
        <endpoint address="http://localhost:9001/MyService"
                  binding="basicHttpBinding"
                bindingConfiguration="BasicHttpEndpointBinding"
                  contract="MyService.IService1" />
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpEndpointBinding">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>

When the client calls the service, it fails and is returned the following exception message:

The HTTP request is unauthorized with client authentication scheme 'Negotiate'.
The authentication header received from the server was 'Negotiate oW8wbaADCgEBom
YEZGBiBgkqhkiG9xIBAgIDAH5TMFGgAwIBBaEDAgEepBEYDzIwMTIwNjI5MTY1NjQ1WqUFAgMOYYqmAw
IBKakRGw9HQVpQUk9NVUsuSU5UUkGqEzARoAMCAQGhCjAIGwZhcG90dHM='.
The remote server returned an error: (401) Unauthorized.
The target principal name is incorrect


 The client config was as follows:

<system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" 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>
    </bindings>
    <client>
      <endpoint address="http://edevd3ap2lonuk:9001/MyService" binding="basicHttpBinding"
          bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
          name="BasicHttpBinding_IService1">
        
      </endpoint>
    </client>
  </system.serviceModel>

This config seems normal and is similar to all the examples online. It should be noted however that most of the online examples are weighted towards a service hosted in IIS whereas this (due to technical restrictions) had to be hosted within a Console application or Windows Service.

The first lesson with WCF errors is to look carefully at the inner exceptions, and not just the exception message but at the headers part too. The clue in this error was the message "The target principal name is incorrect". The first hint came from here.

Additional reading included Security in Windows Communication Foundation and Service Identity and Authentication.

Overriding the service identity is described here:
http://msdn.microsoft.com/en-us/library/bb628618.aspx

Note that it also describes the behaviour when the Service Principal Name is missing:

When SPN or UPN Equals the Empty String

If you set the SPN or UPN equal to an empty string, a number of different things happen, depending on the security level and authentication mode being used:
  • If you are using transport level security, NT LanMan (NTLM) authentication is chosen.
  • If you are using message level security, authentication may fail, depending on the authentication mode:
  • If you are using spnego mode and the AllowNtlm attribute is set to false, authentication fail.
  • If you are using spnego mode and the AllowNtlm attribute is set to true, authentication fails if the UPN is empty, but succeeds if the SPN is empty.
  • If you are using Kerberos direct (also known as "one-shot"), authentication fails.

Solution
The issue was to do with the fact the client did not have a Service Principle Name added.
Modifying the client config to include this fixed the issue:

<system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" 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>
    </bindings>
    <client>
      <endpoint address="http://edevd3ap2lonuk:9001/MyService" binding="basicHttpBinding"
          bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
          name="BasicHttpBinding_IService1">
        <identity>
          <servicePrincipalName value="MYSERVICE/MyMachine"/> 
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>

Almost always the examples are in code which is a little difficult if you need to do it in code. The code solution, using a ChannelFactory, is below:

var client =
channelFactory.CreateChannel(new EndpointAddress(new Uri("http://edevd3ap2lonuk:9001/MyService"), new SpnEndpointIdentity("MYSERVICE/MyMachine")));

No comments:

Post a Comment