1
votes

I am attempting to perform Kerberos authentication from a Client App (Windows Forms) to a WCF Web Service on one Windows 2008 R2 server running under IIS, which in-turn calls another WCF service running on another Windows 2008 R2 server also running under IIS. I've seen this referred to this as a Kerberos Double-Hop Authentication.

When I locate the two web services on the same Windows 2008 R2 server then our double-hop authentication works fine. However, when we move the second WCF service to a different server the authentication fails between the two web services. I do not know what causes this problem which may be a configuration issue, or something in our server/network set-up. Client and servers all exist in the same domain.

Here is more detail of what I have done so far. I've tried many of the suggestions in other related topics/questions about this issue, but no joy so far.

  • I have created a Windows Form client application that connects to a WCF web service (I will refer to this as the Middle service), and return the user name of the logged in user to the client app from the service.
  • In the client app.config (see below) we have specified 'Windows' authentication (pls see below for config).
  • The Middle service runs on our server 'SERVER1'.
  • The Middle service returns the User Name fine to the client app, therefore the Middle service authentication must be working fine.
  • I have another method in the Middle service that calls another WCF web service (I will refer to this second service as the End service), and again Windows authentication is specified on the binding from Middle service to End service. If I locate the End service on the same server as the Middle service then again authentication works as expected, and the User name for the logged in user (ie: me) is returned to the client app from the End service, via the Middle service. However, if I locate the End service on a different server (SERVER2) the authentication (double hop) fails, 'Inner exception:The request for security token could not be satisfied because authentication failed.'
  • The Client app, Middle service and End service all exist in the same Domain (MYDOMAIN).
  • I am using a service account (MYDOMAIN\MY-HOST_ACCOUNT) to run both Middle and End service, and created a Service Principal Name (SPN) for this called 'HTTP/SERVER1.int.mydomain.com'. I have also trusted this service account, and the computer/machine 'SERVER1' for Kerberos delegation in Active Directory (Trust this user/computer for delegation to any service). The Bindings use 'Message' security mode and this is specified in the Web Service bindings.
  • I have included the Client app.config and both Web Service web.configs (pls see below).
  • I have tried a variety of different config settings, and these current settings work fine when the services are located on the same server, but the error occurs when the services are on different servers. I have tried both 'true' and 'false' for 'negotiateServiceCredential', but believe this needs to be 'true'. I get a different error message when this is set to false (The authentication modes using Kerberos do not support the impersonation level 'Delegation'. Specify identification or impersonation).

Hopefully someone who has dealt with 'double hop' WCF Kerberos authentication before may recognise this issue and be able to assist me.

Many thanks

CLIENT APP CONFIG

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IMiddleService">
                    <security mode="Message">
                        <message clientCredentialType="Windows" negotiateServiceCredential="true" establishSecurityContext="true" algorithmSuite="Default" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <behaviors>
          <endpointBehaviors>
            <behavior name="DelegationBehavior">
              <clientCredentials>
                <windows allowedImpersonationLevel="Delegation" />
              </clientCredentials>
            </behavior>
          </endpointBehaviors>
        </behaviors>
        <client>
          <endpoint address="http://SERVER1/KerberosMiddleService/MiddleService.svc"
              behaviorConfiguration="DelegationBehavior" binding="wsHttpBinding"
              bindingConfiguration="WSHttpBinding_IMiddleService" contract="KerberosMiddleService.IMiddleService"
              name="WSHttpBinding_IMiddleService">
            <identity>
              <servicePrincipalName value="HTTP/SERVER1.int.mydomain.com"/>
              <userPrincipalName value="MYDOMAIN\[email protected]"/>
            </identity>
          </endpoint>
        </client>
    </system.serviceModel>
</configuration>

MIDDLE SERVICE WEB CONFIG

<?xml version="1.0"?>
<configuration>
  <appSettings/>
  <system.web>
    <compilation targetFramework="4.0"/>
    <httpRuntime/>
    <customErrors mode="Off"/>
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_IEndService">
          <security mode="Message">
            <message clientCredentialType="Windows" negotiateServiceCredential="true" establishSecurityContext="true" algorithmSuite="Default"/>
          </security>
         </binding>
          <binding name="WSHttpBinding_IEndService1">
          <security mode="Message">
            <message clientCredentialType="Windows" negotiateServiceCredential="true" establishSecurityContext="true" algorithmSuite="Default" />
          </security>
         </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceAuthorization impersonateCallerForAllOperations="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="DelegationBehavior">
         <clientCredentials>
           <windows allowedImpersonationLevel="Delegation" />
         </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <client>
      <endpoint address="http://SERVER2/endservice/endservice.svc" 
                behaviorConfiguration="DelegationBehavior" binding="wsHttpBinding" 
                bindingConfiguration="WSHttpBinding_IEndService" contract="KerberosEndService.IEndService"
                name="WSHttpBinding_IEndService">
      </endpoint>
      <endpoint address="http://SERVER1/kerberosendservice/endservice.svc" 
                behaviorConfiguration="DelegationBehavior" binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_IEndService1" contract="BT01_KerberosEndService.IEndService"
                name="WSHttpBinding_IEndService1">
      </endpoint>
    </client>
    <protocolMapping>
      <add binding="wsHttpBinding" scheme="http"/>
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

END SERVICE WEB CONFIG

<?xml version="1.0"?>
<configuration>
  <appSettings/>
<system.web>
    <customErrors mode="Off"/>
    <compilation targetFramework="4.0"/>
    <httpRuntime/>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceAuthorization impersonateCallerForAllOperations="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add binding="wsHttpBinding" scheme="http"/>
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

A SECTION OF THE WCF STACK TRACE

<ExceptionString>System.ServiceModel.Security.SecurityNegotiationException: The caller was not authenticated by the service. ---&amp;gt; System.ServiceModel.FaultException: The request for security token could not be satisfied because authentication failed.
   at System.ServiceModel.Security.SecurityUtils.ThrowIfNegotiationFault(Message message, EndpointAddress target)
   at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)
   --- End of inner exception stack trace ---</ExceptionString><InnerException><ExceptionType>System.ServiceModel.FaultException, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>The request for security token could not be satisfied because authentication failed.</Message><StackTrace>   at System.ServiceModel.Security.SecurityUtils.ThrowIfNegotiationFault(Message message, EndpointAddress target)
   at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)</StackTrace><ExceptionString>System.ServiceModel.FaultException: The request for security token could not be satisfied because authentication failed.
   at System.ServiceModel.Security.SecurityUtils.ThrowIfNegotiationFault(Message message, EndpointAddress target)
   at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)</ExceptionString></InnerException></Exception></TraceRecord></DataItem></TraceData></ApplicationData></E2ETraceEvent><E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent"><System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system"><EventID>131075</EventID><Type>3</Type><SubType Name="Error">0</SubType><Level>2</Level><TimeCreated SystemTime="2015-03-02T21:04:14.4059347Z" /><Source Name="System.ServiceModel" /><Correlation ActivityID="{acfc80d6-b119-4f57-aaf2-65f1319b9fca}" /><Execution ProcessName="w3wp" ProcessID="1432" ThreadID="43" /><Channel/><Computer>SERVER1</Computer></System><ApplicationData><TraceData><DataItem><TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Error"><TraceIdentifier>http://msdn.microsoft.com/en-NZ/library/System.ServiceModel.Diagnostics.ThrowingException.aspx</TraceIdentifier><Description>Throwing an exception.</Description><AppDomain>/LM/W3SVC/1/ROOT/KerberosMiddleService-6-130698038017642990</AppDomain><Exception><ExceptionType>System.ServiceModel.Security.SecurityNegotiationException, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>The caller was not authenticated by the service.</Message><StackTrace>   at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)
   at System.ServiceModel.Security.IssuanceTokenProviderBase`1.GetNextOutgoingMessage(Message incomingMessage, T negotiationState)
   at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)</StackTrace><ExceptionString>System.ServiceModel.Security.SecurityNegotiationException: The caller was not authenticated by the service. ---&amp;gt; System.ServiceModel.FaultException: The request for security token could not be satisfied because authentication failed.
   at System.ServiceModel.Security.SecurityUtils.ThrowIfNegotiationFault(Message message, EndpointAddress target)
1

1 Answers

1
votes

Ok, you need to add another SPN for the end service as well:

HTTP/SERVER2.int.mydomain.com MYDOMAIN\MY-HOST_ACCOUNT)
HTTP/SERVER2 MYDOMAIN\MY-HOST_ACCOUNT)

It is best to specify both FQDN and Netbios name. Ensure that you don't have duplicate SPN's otherwise Kerberos authentication wont work. Add the SPN's as delegation targets to the domain account (this might not be needed since you are using the same domain account for both servers).

Since the MiddleService needs to impersonate/delegate to the EndService, you need to give the domain account the privileges to do so using the local security policies - Local Policies - User Rights Assignment:

Act as part of the operating system

This user right allows a process to impersonate any user without authentication. The process can therefore gain access to the same local resources as that user.

Impersonate a client after authentication

Assigning this privilege to a user allows programs running on behalf of that user to impersonate a client.

Remember to change your IIS application settings to use the application pool credentials so that the domain account is used for authentication.