Dan Rigsby - Coding Up Style

.Net, C#, & Wcf Development

Impersonate a Client’s Identity in Wcf

Posted by Dan Rigsby on April 17th, 2008

Impersonation is the process of impersonating a user in an application that isn’t necessarily running as that user.  In Wcf, a service may need to impersonate the user of the client in order to access a resource such as a file share, another machine, etc.  The service may even need to call a different Wcf service on the client’s behalf.

First off, you need to be using a binding that supports impersonation. They are:

To setup impersonation you can follow one of two models: Declarative and Imperative.

Declarative Model

The easiest way to setup impersonation is declaratively through a System.ServiceModel.ImpersonationOption.  This is a property of the OperationBehaviorAttribute that indicates the level of impersonation that the operation supports.  There are three options here:
  1. NotAllowed: Impersonation is not performed. If ImpersonateCallerForAllOperations is equal to true, a validation exception occurs at service startup time.
  2. Allowed: Impersonation is performed if credentials are available and ImpersonateCallerForAllOperations is equal to true.
  3. Required: Impersonation is required. This method can’t run unless the user can be impersonated.  The value of ImpersonateCallerForAllOperations doesn’t matter here.  Impersonation always takes place.

The default value is NotAllowed.  So if you are going to use ImpersonateCallerForAllOperations, you will need to make sure to set all of your methods to ImpersonationOption.Allowed.  If you are not using ImpersonateCallerForAllOperations and just want to use a impersonation for a single method, the set ImpersonationOption.Required.

[OperationBehavior(Impersonation=ImpersonationOption.Required)]
public bool MyMethod(string input)
{
    return true;
}

Imperative model

If you chose to setup the impersonation in code, System.ServiceModel.ServiceSerurityContext has everything you need.  But, if the declarative model is so easy to use, why would you want to set this up through code? Well, this model is best used if only a part of your method needs to use impersonation.  The declarative model uses client impersonation for everything in the method.  Depending on what your system is doing, one model may be better than another.

public bool MyMethod(string myParameter)
{
    // Set security context for the current user
    System.ServiceModel.ServiceSecurityContext securityContext =
        System.ServiceModel.ServiceSecurityContext.Current;

    // Make sure there is a windows identity present and impersonate it
    if (securityContext != null
        && securityContext.WindowsIdentity != null)
    {
        // Can check the securityContext.WindowsIdentity.ImpersonationLevel
        // here to make sure we are allowed to impersonate before
        // actually creating the impersonation context

        using (securityContext.WindowsIdentity.Impersonate())
        {
            // TODO: Perform operations here as the impersonated user
        }
    }

    return false;
}
 
Seems easy enough right?  You just grab the current windows identity and create a WindowsImpersonationContext
 

Impersonation for all Methods

If you need to use impersonation for all methods on a service. You can use the ServiceAuthorizationBehavior and set the ImpersonateCallerForAllOperations property to true.  This can be done programmatically on the servicehost like so:

ServiceAuthorizationBehavior serviceAuthoriationBehavior =
    serviceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
serviceAuthoriationBehavior.ImpersonateCallerForAllOperations = true;

Or through the application configuration as such:

<behaviors>
    <serviceBehaviors>
        <behavior name="myBehavior">
            <serviceAuthorization impersonateCallerForAllOperations="true" />
        </behavior>
    </serviceBehaviors>
</behaviors>

Client Configuration

There is a bit more to this story.  The default ImpersonationLevel of the client is set to Identification which doesn’t allow impersonation.  You must setup the client connection to allow impersonation.  Here is a list of the impersonation levels you can use:
  1. None: An impersonation level is not assigned.
  2. Anonymous: The server process cannot obtain identification information about the client, and it cannot impersonate the client.
  3. Identification: The server process can obtain information about the client, such as security identifiers and privileges, but it cannot impersonate the client. This is useful for servers that export their own objects, for example, database products that export tables and views. Using the retrieved client-security information, the server can make access-validation decisions without being able to use other services that are using the client’s security context.
  4. Impersonation: The server process can impersonate the client’s security context on its local system. The server cannot impersonate the client on remote systems.
  5. Delegation: The server process can impersonate the client’s security context on remote systems.

If you need impersonation, you will have to choose between Impersonation or Delegation depending on your needs. 

If you are using a proxy client to connect to the service, you could setup the impersonation level like so:

// Create a new client and set the impersonation level
MyServiceClient client = new MyServiceClient();
client.ClientCredentials.Windows.AllowedImpersonationLevel =
     System.Security.Principal.TokenImpersonationLevel.Impersonation;
 
Using a ChannelFactory is very similar:
 
// Create a new channel factory and set the impersonation level
ChannelFactory<IMyService> channelFactory =
    new ChannelFactory<IMyService>();
channelFactory.Credentials.Windows.AllowedImpersonationLevel =
     System.Security.Principal.TokenImpersonationLevel.Impersonation;
 

Impersonate a totally different Windows User in a client

There may also be times when on the client you want to impersonate a different user than what is currently being used. To do this, you must know the domain, username, and password for that user. You then just use these values to set the client credential for the client:

MyServiceClient client = new MyServiceClient();
client.ClientCredentials.Windows.ClientCredential.Domain = domain;
client.ClientCredentials.Windows.ClientCredential.UserName = username;
client.ClientCredentials.Windows.ClientCredential.Password = password;
client.ClientCredentials.Windows.AllowedImpersonationLevel =
     System.Security.Principal.TokenImpersonationLevel.Impersonation;
 

Or through a ChannelFactory as such:

ChannelFactory<IMyService> channelFactory =
    new ChannelFactory<IMyService>();
channelFactory.Credentials.Windows.ClientCredential.Domain = domain;
channelFactory.Credentials.Windows.ClientCredential.UserName = username;
channelFactory.Credentials.Windows.ClientCredential.Password = password;
channelFactory.Credentials.Windows.AllowedImpersonationLevel =
     System.Security.Principal.TokenImpersonationLevel.Impersonation;
 
You can read more about impersonation here: http://msdn2.microsoft.com/en-us/library/ms730088.aspx

kick it on DotNetKicks.com

 

4 Responses to “Impersonate a Client’s Identity in Wcf”

  1. Dew Drop - April 19, 2008 | Alvin Ashcraft's Morning Dew Says:

    [...] Impersonate a Client’s Identity in WCF (Dan Rigsby) [...]

  2. Yong Hee Park's Blog : WCF & Impersonation (Links) Says:

    [...] http://www.danrigsby.com/blog/index.php/2008/04/17/impersonate-a-clients-identity-in-wcf/ [...]

  3. winzin Says:

    How do I handle Invalid credential?
    In svctraceviewer, the client kept on trying in never ending loop.
    System.ServiceModel.Security.SecurityNegotiationException exception was thrown from WCF service “The server has rejected the client credentials.” without sending reply back from service to client until client times out.

  4. Dan Rigsby Says:

    You could create a FaultException to return to the client.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>