Delegation and Impersonation with WCF

Impersonation is a common technique that services use to restrict client access to a service domain's resources. Service domain resources can either be machine resources, such as local files (impersonation), or a resource on another machine, such as a file share (delegation). For a sample application, see Impersonating the Client. For an example of how to use impersonation, see How to: Impersonate a Client on a Service.

Important

Be aware that when impersonating a client on a service, the service runs with the client's credentials, which may have higher privileges than the server process.

Overview

Typically, clients call a service to have the service perform some action on the client’s behalf. Impersonation allows the service to act as the client while performing the action. Delegation allows a front-end service to forward the client’s request to a back-end service in such a way that the back-end service can also impersonate the client. Impersonation is most commonly used as a way of checking whether a client is authorized to perform a particular action, while delegation is a way of flowing impersonation capabilities, along with the client’s identity, to a back-end service. Delegation is a Windows domain feature that can be used when Kerberos-based authentication is performed. Delegation is distinct from identity flow and, because delegation transfers the ability to impersonate the client without possession of the client’s password, it is a much higher privileged operation than identity flow.

Both impersonation and delegation require that the client have a Windows identity. If a client does not possess a Windows identity, then the only option available is to flow the client’s identity to the second service.

Impersonation Basics

Windows Communication Foundation (WCF) supports impersonation for a variety of client credentials. This topic describes service model support for impersonating the caller during the implementation of a service method. Also discussed are common deployment scenarios involving impersonation and SOAP security and WCF options in these scenarios.

This topic focuses on impersonation and delegation in WCF when using SOAP security. You can also use impersonation and delegation with WCF when using transport security, as described in Using Impersonation with Transport Security.

Two Methods

WCF SOAP security has two distinct methods for performing impersonation. The method used depends on the binding. One is impersonation from a Windows token obtained from the Security Support Provider Interface (SSPI) or Kerberos authentication, which is then cached on the service. The second is impersonation from a Windows token obtained from the Kerberos extensions, collectively called Service-for-User (S4U).

Cached Token Impersonation

You can perform cached-token impersonation with the following:

S4U-Based Impersonation

You can perform S4U-based impersonation with the following:

  • WSHttpBinding, WSDualHttpBinding, and NetTcpBinding with a certificate client credential that the service can map to a valid Windows account.

  • Any CustomBinding that uses a Windows client credential with the requireCancellation property set to false.

  • Any CustomBinding that uses a user name or Windows client credential and secure conversation with the requireCancellation property set to false.

The extent to which the service can impersonate the client depends on the privileges the service account holds when it attempts impersonation, the type of impersonation used, and possibly the extent of impersonation the client permits.

Note

When the client and service are running on the same computer and the client is running under a system account (for example, Local System or Network Service), the client cannot be impersonated when a secure session is established with stateful Security Context tokens. A Windows Form or console application typically runs under the currently logged-in account, so that account can be impersonated by default. However, when the client is an ASP.NET page and that page is hosted in IIS 6.0 or IIS 7.0, then the client does run under the Network Service account by default. All of the system-provided bindings that support secure sessions use a stateless security context token (SCT) by default. However, if the client is an ASP.NET page, and secure sessions with stateful SCTs are used, the client cannot be impersonated. For more information about using stateful SCTs in a secure session, see How to: Create a Security Context Token for a Secure Session.

Impersonation in a Service Method: Declarative Model

Most impersonation scenarios involve executing the service method in the caller context. WCF provides an impersonation feature that makes this easy to do by allowing the user to specify the impersonation requirement in the OperationBehaviorAttribute attribute. For example, in the following code, the WCF infrastructure impersonates the caller before executing the Hello method. Any attempt to access native resources inside the Hello method succeed only if the access control list (ACL) of the resource allows the caller access privileges. To enable impersonation, set the Impersonation property to one of the ImpersonationOption enumeration values, either ImpersonationOption.Required or ImpersonationOption.Allowed, as shown in the following example.

Note

When a service has higher credentials than the remote client, the credentials of the service are used if the Impersonation property is set to Allowed. That is, if a low-privileged user provides its credentials, a higher-privileged service executes the method with the credentials of the service, and can use resources that the low-privileged user would otherwise not be able to use.

[ServiceContract]
public interface IHelloContract
{
    [OperationContract]
    string Hello(string message);
}

public class HelloService : IHelloService
{
    [OperationBehavior(Impersonation = ImpersonationOption.Required)]
    public string Hello(string message)
    {
        return "hello";
    }
}

<ServiceContract()> _
Public Interface IHelloContract
    <OperationContract()> _
    Function Hello(ByVal message As String) As String
End Interface


Public Class HelloService
    Implements IHelloService

    <OperationBehavior(Impersonation:=ImpersonationOption.Required)> _
    Public Function Hello(ByVal message As String) As String Implements IHelloService.Hello
        Return "hello"
    End Function
End Class

The WCF infrastructure can impersonate the caller only if the caller is authenticated with credentials that can be mapped to a Windows user account. If the service is configured to authenticate using a credential that cannot be mapped to a Windows account, the service method is not executed.

Note

On Windows XP, impersonation fails if a stateful SCT is created, resulting in an InvalidOperationException. For more information, see Unsupported Scenarios.

Impersonation in a Service Method: Imperative Model

Sometimes a caller does not need to impersonate the entire service method to function, but for only a portion of it. In this case, obtain the Windows identity of the caller inside the service method and imperatively perform the impersonation. Do this by using the WindowsIdentity property of the ServiceSecurityContext to return an instance of the WindowsIdentity class and calling the Impersonate method before using the instance.

Note

Be sure to use the Visual BasicUsing statement or the C# using statement to automatically revert the impersonation action. If you do not use the statement, or if you use a programming language other than Visual Basic or C#, be sure to revert the impersonation level. Failure to do this can form the basis for denial of service and elevation of privilege attacks.

public class HelloService : IHelloService
{
    [OperationBehavior]
    public string Hello(string message)
    {
        WindowsIdentity callerWindowsIdentity =
        ServiceSecurityContext.Current.WindowsIdentity;
        if (callerWindowsIdentity == null)
        {
            throw new InvalidOperationException
           ("The caller cannot be mapped to a WindowsIdentity");
        }
        using (callerWindowsIdentity.Impersonate())
        {
            // Access a file as the caller.
        }
        return "Hello";
    }
}
Public Class HelloService
    Implements IHelloService

    <OperationBehavior()> _
    Public Function Hello(ByVal message As String) As String _
       Implements IHelloService.Hello
        Dim callerWindowsIdentity As WindowsIdentity = _
            ServiceSecurityContext.Current.WindowsIdentity
        If (callerWindowsIdentity Is Nothing) Then
            Throw New InvalidOperationException( _
              "The caller cannot be mapped to a WindowsIdentity")
        End If
        Dim cxt As WindowsImpersonationContext = callerWindowsIdentity.Impersonate()
        Using (cxt)
            ' Access a file as the caller.
        End Using

        Return "Hello"

    End Function
End Class

Impersonation for All Service Methods

In some cases, you must perform all the methods of a service in the caller’s context. Instead of explicitly enabling this feature on a per-method basis, use the ServiceAuthorizationBehavior. As shown in the following code, set the ImpersonateCallerForAllOperations property to true. The ServiceAuthorizationBehavior is retrieved from the collections of behaviors of the ServiceHost class. Also note that the Impersonation property of the OperationBehaviorAttribute applied to each method must also be set to either Allowed or Required.

// Code to create a ServiceHost not shown.
ServiceAuthorizationBehavior MyServiceAuthorizationBehavior =
    serviceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
MyServiceAuthorizationBehavior.ImpersonateCallerForAllOperations = true;
' Code to create a ServiceHost not shown.
Dim MyServiceAuthorizationBehavior As ServiceAuthorizationBehavior
MyServiceAuthorizationBehavior = serviceHost.Description.Behaviors.Find _
(Of ServiceAuthorizationBehavior)()
MyServiceAuthorizationBehavior.ImpersonateCallerForAllOperations = True

The following table describes WCF behavior for all possible combinations of ImpersonationOption and ImpersonateCallerForAllServiceOperations.

ImpersonationOption ImpersonateCallerForAllServiceOperations Behavior
Required n/a WCF impersonates the caller
Allowed false WCF does not impersonate the caller
Allowed true WCF impersonates the caller
NotAllowed false WCF does not impersonate the caller
NotAllowed true Disallowed. (An InvalidOperationException is thrown.)

Impersonation Level Obtained from Windows Credentials and Cached Token Impersonation

In some scenarios the client has partial control over the level of impersonation the service performs when a Windows client credential is used. One scenario occurs when the client specifies an Anonymous impersonation level. The other occurs when performing impersonation with a cached token. This is done by setting the AllowedImpersonationLevel property of the WindowsClientCredential class, which is accessed as a property of the generic ChannelFactory<TChannel> class.

Note

Specifying an impersonation level of Anonymous causes the client to log on to the service anonymously. The service must therefore allow anonymous logons, regardless of whether impersonation is performed.

The client can specify the impersonation level as Anonymous, Identification, Impersonation, or Delegation. Only a token at the specified level is produced, as shown in the following code.

ChannelFactory<IEcho> cf = new ChannelFactory<IEcho>("EchoEndpoint");
cf.Credentials.Windows.AllowedImpersonationLevel  =
    System.Security.Principal.TokenImpersonationLevel.Impersonation;
Dim cf As ChannelFactory(Of IEcho) = New ChannelFactory(Of IEcho)("EchoEndpoint")
cf.Credentials.Windows.AllowedImpersonationLevel = _
System.Security.Principal.TokenImpersonationLevel.Impersonation

The following table specifies the impersonation level the service obtains when impersonating from a cached token.

AllowedImpersonationLevel value Service has SeImpersonatePrivilege Service and client are capable of delegation Cached token ImpersonationLevel
Anonymous Yes n/a Impersonation
Anonymous No n/a Identification
Identification n/a n/a Identification
Impersonation Yes n/a Impersonation
Impersonation No n/a Identification
Delegation Yes Yes Delegation
Delegation Yes No Impersonation
Delegation No n/a Identification

Impersonation Level Obtained from User Name Credentials and Cached Token Impersonation

By passing the service its user name and password, a client enables WCF to log on as that user, which is equivalent to setting the AllowedImpersonationLevel property to Delegation. (The AllowedImpersonationLevel is available on the WindowsClientCredential and HttpDigestClientCredential classes.) The following table provides the impersonation level obtained when the service receives user name credentials.

AllowedImpersonationLevel Service has SeImpersonatePrivilege Service and client are capable of delegation Cached token ImpersonationLevel
n/a Yes Yes Delegation
n/a Yes No Impersonation
n/a No n/a Identification

Impersonation Level Obtained from S4U-Based Impersonation

Service has SeTcbPrivilege Service has SeImpersonatePrivilege Service and client are capable of delegation Cached token ImpersonationLevel
Yes Yes n/a Impersonation
Yes No n/a Identification
No n/a n/a Identification

Mapping a Client Certificate to a Windows Account

It is possible for a client to authenticate itself to a service using a certificate, and to have the service map the client to an existing account through Active Directory. The following XML shows how to configure the service to map the certificate.

<behaviors>  
  <serviceBehaviors>  
    <behavior name="MapToWindowsAccount">  
      <serviceCredentials>  
        <clientCertificate>  
          <authentication mapClientCertificateToWindowsAccount="true" />  
        </clientCertificate>  
      </serviceCredentials>  
    </behavior>  
  </serviceBehaviors>  
</behaviors>  

The following code shows how to configure the service.

// Create a binding that sets a certificate as the client credential type.  
WSHttpBinding b = new WSHttpBinding();  
b.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;  
  
// Create a service host that maps the certificate to a Windows account.  
Uri httpUri = new Uri("http://localhost/Calculator");  
ServiceHost sh = new ServiceHost(typeof(HelloService), httpUri);  
sh.Credentials.ClientCertificate.Authentication.MapClientCertificateToWindowsAccount = true;  

Delegation

To delegate to a back-end service, a service must perform Kerberos multi-leg (SSPI without NTLM fallback) or Kerberos direct authentication to the back-end service using the client’s Windows identity. To delegate to a back-end service, create a ChannelFactory<TChannel> and a channel, and then communicate through the channel while impersonating the client. With this form of delegation, the distance at which the back-end service can be located from the front-end service depends on the impersonation level achieved by the front-end service. When the impersonation level is Impersonation, the front-end and back-end services must be running on the same machine. When the impersonation level is Delegation, the front-end and back-end services can be on separate machines or on the same machine. Enabling delegation-level impersonation requires that Windows domain policy be configured to permit delegation. For more information about configuring Active Directory for delegation support, see Enabling Delegated Authentication.

Note

When a client authenticates to the front-end service using a user name and password that correspond to a Windows account on the back-end service, the front-end service can authenticate to the back-end service by reusing the client’s user name and password. This is a particularly powerful form of identity flow, because passing user name and password to the back-end service enables the back-end service to perform impersonation, but it does not constitute delegation because Kerberos is not used. Active Directory controls on delegation do not apply to user name and password authentication.

Delegation Ability as a Function of Impersonation Level

Impersonation level Service can perform cross-process delegation Service can perform cross-machine delegation
Identification No No
Impersonation Yes No
Delegation Yes Yes

The following code example demonstrates how to use delegation.

public class HelloService : IHelloService
{
    [OperationBehavior(Impersonation = ImpersonationOption.Required)]
    public string Hello(string message)
    {
        WindowsIdentity callerWindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity;
        if (callerWindowsIdentity == null)
        {
            throw new InvalidOperationException
             ("The caller cannot be mapped to a Windows identity.");
        }
        using (callerWindowsIdentity.Impersonate())
        {
            EndpointAddress backendServiceAddress = new EndpointAddress("http://localhost:8000/ChannelApp");
            // Any binding that performs Windows authentication of the client can be used.
            ChannelFactory<IHelloService> channelFactory = new ChannelFactory<IHelloService>(new NetTcpBinding(), backendServiceAddress);
            IHelloService channel = channelFactory.CreateChannel();
            return channel.Hello(message);
        }
    }
}
Public Class HelloService
    Implements IHelloService

    <OperationBehavior(Impersonation:=ImpersonationOption.Required)> _
    Public Function Hello(ByVal message As String) As String Implements IHelloService.Hello
        Dim callerWindowsIdentity As WindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity
        If (callerWindowsIdentity Is Nothing) Then
            Throw New InvalidOperationException("The caller cannot be mapped to a Windows identity.")
        End If

        Dim backendServiceAddress As EndpointAddress = New EndpointAddress("http://localhost:8000/ChannelApp")
        ' Any binding that performs Windows authentication of the client can be used.
        Dim channelFactory As ChannelFactory(Of IHelloService) = _
          New ChannelFactory(Of IHelloService)(New NetTcpBinding(), backendServiceAddress)
        Dim channel As IHelloService = channelFactory.CreateChannel()
        Return channel.Hello(message)
    End Function
End Class

How to Configure an Application to Use Constrained Delegation

Before you can use constrained delegation, the sender, receiver, and the domain controller must be configured to do so. The following procedure lists the steps that enable constrained delegation. For details about the differences between delegation and constrained delegation, see the portion of Windows Server 2003 Kerberos Extensions that discusses constrained discussion.

  1. On the domain controller, clear the Account is sensitive and cannot be delegated check box for the account under which the client application is running.

  2. On the domain controller, select the Account is trusted for delegation check box for the account under which the client application is running.

  3. On the domain controller, configure the middle tier computer so that it is trusted for delegation, by clicking the Trust computer for delegation option.

  4. On the domain controller, configure the middle tier computer to use constrained delegation, by clicking the Trust this computer for delegation to specified services only option.

For more detailed instructions about configuring constrained delegation, see Kerberos Protocol Transition and Constrained Delegation.

See also