Click here to Skip to main content
15,884,388 members
Articles / Programming Languages / C#

How to enable multi-hop impersonation using constrained delegation in .NET and Active Directory

,
Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
18 Aug 2009CPOL5 min read 69.3K   18   10
A step-by-step guide to help solve a common authentication problem faced by .NET multi-tier developers.

Introduction

Consider the following scenario:

  • Client machine: XP.
  • Business Tier server machine: Windows Server 2008.
  • Database server machine: Windows Server 2008/SQL Server 2008.
  • Client application is a WCF client using net.tcp binding to access the business tier.
  • The business tier operations connect to SQL Server using normal SQL client connections.
  • The application as a whole runs within an Active Directory domain, and is using a single-login approach, such that the end-user should not have to login to the application.

If you are experiencing the common multi-hop authentication problem where the client credentials do not make it past the middle tier -- in other words, the WCF Operation thread runs under the credentials of the Windows client user -- but then the business tier tries to authenticate against SQL Server as ‘NT Authority\Anonymous User’, you can use the following setup to get impersonation working from your business tier.

The client machine is running a .NET 3.5 client application.

The client communicates to the business tier over WCF, and the endpoints are configured for Delegation.

Please note that this information was compiled from a multitude of sites and forums. Too many to list. We got bits and pieces of info from all over the net, but not one had it all. Thus we put together an article that has every step we did to make this work.

XML
<endpointBehaviors>
    <behavior name="defaultClientBehavior">
        <clientCredentials>
            <windows allowNtlm="false"
                     allowedImpersonationLevel="Delegation" />
            <httpDigest impersonationLevel="Delegation" />
        </clientCredentials>
    </behavior>
</endpointBehaviors>

<bindings>
    <netTcpBinding>
        <binding name="defaultBinding"
                 openTimeout="02:01:00"
                 receiveTimeout="02:10:00"
                 closeTimeout="02:02:00"
                 sendTimeout="02:02:00"
                 maxBufferSize="2147483647"
                 maxReceivedMessageSize="2147483647">
            <readerQuotas maxStringContentLength="5242880" />
            <security mode="Transport">
                <transport clientCredentialType="Windows"/>
            </security>
        </binding>
    </netTcpBinding>
</bindings>
<client>
    <endpoint address="net.tcp://businessServer.domain.local:51005/TestService"
              binding="netTcpBinding"
              bindingConfiguration="defaultBinding"
               behaviorConfiguration="defaultClientBehavior"
              contract="Business.Common.Services.ITestService"
              name="Business.Common.Services.ITestService">
        <identity>
            <servicePrincipalName value="TestService/ businessServer.domain.local " />
        </identity>
    </endpoint>
</client>

The server machine runs a set of WCF Services that are hosted in a Windows Service.

The WCF Services use an ORM layer to communicate to SQL Server using the normal ADO.NET SQL Server Provider.

The WCF Service operations are configured for impersonation. What this method does is not important here. The only thing you need to care about are the OperationBehavior attribute and the call to WindowsIdentity.Impersonate:

C#
[OperationBehavior( Impersonation = ImpersonationOption.Required )]
public bool SaveTest( ref Core hc, ref IList<testentity /> testList )
{
  using ( ServiceSecurityContext.Current.WindowsIdentity.Impersonate( ) )
  {
    hc.Errors = new List<error>( );
    using ( ISession session = SessionFactory.GetSession( new ValidationInterceptor( hc ) ) )
    {
      using ( ITransaction transaction = session.BeginTransaction( ) )
      {
        foreach ( TestEntity alias in testList )
        {
          RepositoryFactory
                .Instance
                .Factory<testentity>( new ValidationInterceptor( hc ) 
                   as IInterceptor ).Save( session, transaction, hc, alias );
        }
        transaction.Commit( );
      }
    }
    return true;
  }
}

The WCF Service Bindings are configured for Windows Authentication:

XML
<behaviors>
    <serviceBehaviors>
        <behavior name="defaultBehavior">
            <serviceMetadata httpGetEnabled="false" />
            <serviceAuthorization impersonateCallerForAllOperations="true" />
            <serviceCredentials>
                <windowsAuthentication includeWindowsGroups="true" allowAnonymousLogons="false"/>
            </serviceCredentials>
        </behavior>
</behaviors>
<bindings>
    <netTcpBinding>
        <binding name="defaultBinding"
                 closeTimeout="02:02:00"
                 openTimeout="02:01:00"
                 receiveTimeout="02:10:00"
                 sendTimeout="02:02:00"
                 maxBufferSize="2147483647"
                 maxReceivedMessageSize="2147483647">
            <readerQuotas maxStringContentLength="5242880" />
            <reliableSession inactivityTimeout="02:10:00" />
            <security mode="Transport">
                <transport clientCredentialType="Windows"/>
            </security>
        </binding>
    </netTcpBinding>
</bindings>

<services>
    <service name="Business.Common.Services.TestService"
        behaviorConfiguration="defaultBehavior">
        <endpoint address="net.tcp://businessServer.domain.local:51005/TestService"
                  binding="netTcpBinding"
                  bindingConfiguration="defaultBinding"
                  name=" Business.Common.Services.ITestService"
                  contract="Business.Common.Services.ITestService"
                 >
            <identity>
                <servicePrincipalName value="TestService/businessServer.domain.local"/>
            </identity>
        </endpoint>
    </service>

Now for the yucky stuff. The Active Directory configuration. It has been our experience that sometimes after running through the following steps, there may be a long delay before everything takes effect.

Follow these steps and you should be able to get impersonation working:

  1. On the business server:
    1. Open a command prompt (Run as Administrator).
    2. Run the cliconfg command.

      1. Make sure that TCP/IP is on the top of the Enabled protocols by order list.
      2. Make sure that the Enabled shared memory protocol is un-checked.
      3. Click OK.
    3. Go to Administrative Tools.
    4. Select Local Security Policy. Expand Local Policies.

    5. Select User Rights Assignment.
      1. From the right pane, double-click Impersonate a client after authentication.
        1. Add User or Group.
        2. In the Enter the object names to select box:

          1. Enter the name of the account under which business services are running (this should be the same account that will be used in the SETSPN command below).
          2. Click Check Names.
          3. Select the name once it is resolved to the full account name.
          4. Click OK.
      2. You will be back at the Impersonate a client after authentication Properties dialog box.
      3. Click OK.
  2. On SQL Server:
  3. Open SQL Server Configuration Manager.

    1. Expand the SQL Native Client 10.0 Configuration node (32 bit).
    2. Right click Client Protocols and select Properties.
    3. Make sure that TCP/IP is on the top of the Enabled Protocols list.

    4. Do the same for the SQL Native Client 10.0 Configuration node.
    5. Go to SQL Server Network Configuration.
    6. Select protocols for MSSQLSERVER.

      1. In the list on the right pane, make sure that Shared Memory and TCP/IP are enabled.
      2. Named Pipes and VIA should be disabled.
  4. On the DC for the domain where the business services run, or where the service account user resides:
  5. Open a command prompt (Run as Administrator). Run the following commands:

    1. SETSPN -A TestService/<business-service-host-name>.<fully-qualified-domain-name> <business-service-user-account-name>
    2. SETSPN -A MSSQLSvc/<sql-server-host-name>.<fully-qualified-domain-name>:1433 <sql-service-user-account-name>
  6. In AD Users and Computers:
    1. Go to the Properties window for the account that the business services run as.
    2. Select the Delegation tab.
      1. Select Trust this user for delegation to specified services only (this is constrained delegation).
        1. Select Use Kerberos only.
        2. Click the Add... button.
        3. Click the Users or Computers... button.
        4. In the Enter the object names to select box:

          1. Enter the name of the account under which SQL Server is running (this should be the same account that was used in the SETSPN command above).
          2. Click Check Names.
          3. Select the name once it is resolved to the full account name.
          4. Click OK.
          5. Then you will be back at the Add Services dialog box.
            1. Select the MSSQLSvc Service type from the Available services list.
            2. Click OK.
      2. Then you will be back to the account properties window.
      3. Click Apply.
      4. Click OK.
    3. Go to the properties window for the account that the SQL Server service runs as.
    4. Select the Delegation tab.
      1. Select Trust this user for delegation to any service (Kerberos only).
      2. Click Apply.
      3. Click OK.
    5. Go to the properties window for the computer account of the business server.
    6. Select the Delegation tab.
      1. Make sure that Select Trust this computer for delegation to any service (Kerberos only) is already selected.
      2. Click Apply.
      3. Click OK.
    7. Go to the Properties window for the computer account of SQL Server.
    8. Select the Delegation tab.
      1. Make sure that the Select Trust this computer for delegation to any service (Kerberos only) is already selected.
      2. Click Apply.
      3. Click OK.

What's next for us?

Sravan and I have gained a lot of experience in using WPF to talk to a WCF business tier that uses nHibernate to talk to a database. We've solved the problems of Lazy Loading, Reporting, and general best practices for the client tier when using WCF. So we'll be putting together an article on how to build an application using these three common technologies.

History

  • 8/14/2009: First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United States United States
khadden = C#

Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionCan you Guys share working sample for this. Pin
Manjunathabe01127-Mar-12 18:10
Manjunathabe01127-Mar-12 18:10 
GeneralDouble hop issue within same domain Pin
dudehari27-Oct-10 5:58
dudehari27-Oct-10 5:58 
GeneralRe: Double hop issue within same domain Pin
Ken Hadden27-Oct-10 7:04
Ken Hadden27-Oct-10 7:04 
QuestionRe: Double hop issue within same domain Pin
dudehari27-Oct-10 7:40
dudehari27-Oct-10 7:40 
AnswerRe: Double hop issue within same domain Pin
Ken Hadden27-Oct-10 9:58
Ken Hadden27-Oct-10 9:58 
GeneralRe: Double hop issue within same domain Pin
dudehari27-Oct-10 11:07
dudehari27-Oct-10 11:07 
GeneralRe: Double hop issue within same domain Pin
jfbeaulieu801-May-12 4:10
jfbeaulieu801-May-12 4:10 
GeneralNice Work - Having problem with passing original caller Pin
WhiteKnight30-Aug-09 16:19
WhiteKnight30-Aug-09 16:19 
GeneralRe: Nice Work - Having problem with passing original caller Pin
Ken Hadden31-Aug-09 4:18
Ken Hadden31-Aug-09 4:18 
GeneralRe: Nice Work - Having problem with passing original caller Pin
Ken Hadden8-Sep-09 18:40
Ken Hadden8-Sep-09 18:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.