Impersonation with Double Identities

There are two different levels of security that you can use for WCF services: Message level and Transport level. For Transport level, in HTTP case, you can use different IIS Authentication mechanisms to perform client authentication. If you do not carefully set the settings of the WCF binding, you may end up with both security levels enabled. This has a direct impact of impersonation. The reason is because WCF does not know which identity you want to apply to impersonation.

Generally, it is not recommended to have double securities enabled. However, there is actually a reason why people could get into this dilemma:

  • It is normal for people to disable anonymous access in IIS. In WCF, we want to make sure that a service explicitly specifies its transport level security settings. In this case, it means that you have to configure the service to not use anonymous authentication at transport layer. So when you also want to enable message-level security to perform message signing or encryption, you would get into this double-identity situation.

Here is a simple custom binding configuration that demonstrates how it is the case of double identities:

      <customBinding>

        <binding name="mixedSecureBinding">

          <security/>

          <httpTransport authenticationScheme="Ntlm"/>

        </binding>

      </customBinding>

It enables both message-level security and it also enables the NTLM authentication at the transport layer.

In this case, if you have an operation which requires impersonation, you would get the following error on the server side (you can see it from a debugger):

System.InvalidOperationException: Cannot start impersonation because the SecurityContext for the UltimateReceiver role from the request message with the 'https://www.microsoft.com/test/IHelloContract/Hello' action is not mapped to a Windows identity.

This is because WCF actually set the ServiceSecurityContext to WindowsIdentity.Anonymous when multiple identifies are found.

There are three different ways to resolve this issue.

1. Enable Anonymous Access from IIS

This is the simplest way, though there is a security concern for this approach. Here is the binding configuration that does not have the transport-layer authentication enabled:

      <customBinding>

        <binding name="mixedSecureBinding">

          <security/>

          <httpTransport/>

        </binding>

      </customBinding>

You have to enable the anonymous access in IIS to make this work, otherwise you would get ServiceActivationException on the server side due to mismatched settings between WCF binding and IIS.

This is not the ideal solution if you have other services in the same application that you want to have anonymous access disabled for security purpose.

2. Use Imperative Impersonation

When I said impersonation would fail above, I meant declarative (or automatic) impersonation. Here is the sample service operation that does this:

[OperationBehavior(Impersonation = ImpersonationOption.Required)]

public string Hello(string greeting)

To use imperative impersonation, you need to remove the “Impersonation” setting from the above declaration and perform impersonation from code. This requires having a Windows identity being retrieved from the context. Fortunately WCF provides a way for you to find the identities that are available in the stack:

OperationContext.Current.ServiceSecurityContext.AuthorizationContext.Properties["Identities"]

You can cast this into List<IIdentity> and examine each identity of them. You cannot tell which identity is from which layer though. However, in WCF V1, the identity from the transport layer is always the first one. But this is guaranteed in future versions.

Once you have the Windows identity, you can perform the impersonation by yourself:

using (identity.Impersonate())

{

    // Do something

}

3. Remove Unnecessary Identities Using Custom Authorization Policy

If you are still not satisfied by the above approaches, here is another choice for you. You need to use the ServiceAuthorizationBehavior.ExternalAuthorizationPolicies extension point to add an extra authorization policy to remove identities which are not needed. Here is a good sample about how to implement a custom authorization policy: https://msdn2.microsoft.com/en-gb/library/aa702720.aspx.

In our case, we can implement the Evaluate() method of the custom MyAuthorizationPolicy as following so that the last identity is picked:

public bool Evaluate(EvaluationContext evaluationContext, ref object state)

{

    IList<IIdentity> identities = evaluationContext.Properties["Identities"] as IList<IIdentity>;

    if (identities != null && identities.Count > 1)

    {

        // Pick the last one (which is likely to be the identity embeded in the message.

        evaluationContext.Properties["Identities"] =

            new List<IIdentity>(new IIdentity[] { identities[identities.Count - 1] });

    }

    return true;

}

Now you can configure the Authorization behavior in your ServiceHost. You need to implement ServiceHostFactory so that this custom behavior can be added to the ServiceHost. Here is an implementation of the CreateServiceHost method:

public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)

{

    ServiceHost serviceHost = new ServiceHost(typeof(HelloService), baseAddresses);

    serviceHost.Authorization.ExternalAuthorizationPolicies = new ReadOnlyCollection<IAuthorizationPolicy>(

        new IAuthorizationPolicy[] {

            new MyAuthorizationPolicy()

        });

    return serviceHost;

}

The sample code for this blog entry is attached.

DoubleIdentitiesImp.zip