Building Scalable and Secure WCF Services

The key to building scalable WCF services is to eliminate binding configurations that could result in server affinity.  For this reason you should avoid bindings that establish a session with the service, such as NetTcpBinding or WsHttpBinding with secure conversation enabled.  Both BasicHttpBinding and WebHttpBinding, however, are sessionless and allow you to call a service multiple times without concern for which physical server responds to the call.  This means the load can be more efficiently distributed across multiple servers.

Download the code for this blog post here.

Nevertheless, there is one wrinkle: by default the WCF HTTP bindings enable Keep-Alive, which can result in server affinity and thereby impede scalability in a load-balanced environment.  Here is a snapshot of 5 calls to a service with HTTP Keep-Alive enabled over SSL.  Notice the tunneling only takes place once because a consistent connection is maintained with the server.  This is fine and dandy when clients are talking to the same back-end server and the SSL handshake only takes place when the connection is established.

fiddler-keep-alive-true

Here is a snapshot of the same five calls but with Keep-Alive disabled. Notice how the handshake takes place each time the service is called.

fiddler-keep-alive-false

To disable Keep-Alive, you need to create a custom binding, because this setting cannot be configured for the standard HTTP bindings.  Here is a web.config file with custom SOAP and REST bindings and Keep-Alive disabled.

<configuration>

  <system.serviceModel>
    
    <services>
      <service name="SecureTransportDemo.Service.GreetingService">
        <endpoint address="Soap"
                  binding="customBinding" 
                  bindingConfiguration="soap-secure-nokeepalive"
                  contract="SecureTransportDemo.Service.IGreetingService"
                  name="soap-nokeepalive"/>
        <endpoint address="Rest"
                  binding="customBinding"
                  bindingConfiguration="rest-secure-nokeepalive"
                  behaviorConfiguration="web"
                  contract="SecureTransportDemo.Service.IGreetingService"
                  name="rest-nokeepalive"/>
      </service>
    </services>
    <bindings>
      <customBinding>
        <binding name="soap-secure-nokeepalive">
          <textMessageEncoding />
          <httpsTransport allowCookies="false" 
                          keepAliveEnabled="false"/>
        </binding>
        <binding name="rest-secure-nokeepalive">
          <webMessageEncoding />
          <httpsTransport manualAddressing="true" 
                          allowCookies="false" 
                          keepAliveEnabled="false"/>
        </binding>
      </customBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp automaticFormatSelectionEnabled="true"
                   faultExceptionEnabled="true"
                   helpEnabled="true"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

There’s a problem however – we’re still not getting optimal efficiency for a load-balanced environment.  The ideal solution would be a load balancer in the DMZ that would accept requests from clients over SSL with Keep-Alive enabled, but would turn around and communicate with backend servers over a non-secure channel with Keep-Alive disabled.  For that we need a load balancer capable of SSL pass-through, such as F5’s BIG-IP. (Note that you’ll still want to secure the communication between the load balancer and the service using a mechanism such as IPSec, so that privacy is also maintained inside the corporate firewall.)

ssl-pass-thru

The problem here is that WCF will not allow you to pass credentials, such as username and password, over a non-secure channel. And for good reason: credentials sent in the clear could be intercepted. However, presuming it is safe to send credentials from the load balancer to the backend service from behind a firewall, this is exactly what we want to do.  The trick is to fool WCF into thinking we are using a secure channel when in fact we are not.

Michele Leroux Bustamante has written an excellent article showing precisely how to do this.  It entails creating a custom HttpTransportBindingElement that can assert security capabilities indicating encryption and signing at the transport level when in fact this is not the case.  Essentially the custom binding element has a GetProperty method which returns an implementation of ISecurityCapabilities asserting that a protection level of EncryptAndSign is supported.

public class NonSslAuthHttpTransportBindingElement : HttpTransportBindingElement
{
    public override BindingElement Clone()
    {
        return new NonSslAuthHttpTransportBindingElement
            {
                AuthenticationScheme = AuthenticationScheme,
                ManualAddressing = ManualAddressing
            };
    }

    public override T GetProperty<T>(BindingContext context)
    {
        if (typeof(T) == typeof(ISecurityCapabilities))
        {
            return (T)(object)new NonSslAuthSecurityCapabilities();
        }
        return base.GetProperty<T>(context);
    }
}
public class NonSslAuthSecurityCapabilities : ISecurityCapabilities
{
    public ProtectionLevel SupportedRequestProtectionLevel
    {
        get { return ProtectionLevel.EncryptAndSign; }
    }

    public ProtectionLevel SupportedResponseProtectionLevel
    {
        get { return ProtectionLevel.EncryptAndSign; }
    }

    public bool SupportsClientAuthentication
    {
        get { return false; }
    }

    public bool SupportsClientWindowsIdentity
    {
        get { return false; }
    }

    public bool SupportsServerAuthentication
    {
        get { return true; }
    }
}

The next step would be to create a class extending HttpTransportElement, so that you can specify the transport element for the custom binding in the config file of the service host.

public class NonSslAuthTransportElement : HttpTransportElement
{
    public override Type BindingElementType
    {
        get { return typeof(NonSslAuthHttpTransportBindingElement); }
    }

    protected override TransportBindingElement CreateDefaultBindingElement()
    {
        return new NonSslAuthHttpTransportBindingElement();
    }
}

For this you’ll need to define a binding element extension in the config file that transport element type.  Here is what the service config file looks like with the extension defined and used in a custom binding.

<extensions>
  <bindingElementExtensions>
    <add name="nonSslAuthTransport"
          type="NonSslAuthSecurity.NonSslAuthTransportElement, NonSslAuthSecurity"/>
  </bindingElementExtensions>
</extensions>

<bindings>
  <customBinding>
    <binding name="nonSslAuthBinding">
      <binaryMessageEncoding />
      <security authenticationMode="UserNameOverTransport"/>
      <nonSslAuthTransport authenticationScheme="Anonymous"
                            allowCookies="false"
                            keepAliveEnabled="false"/>
    </binding>
    <binding name="webNonSslAuthBinding">
      <webMessageEncoding />
      <nonSslAuthTransport authenticationScheme="Basic"
                            manualAddressing="true"
                            allowCookies="false"
                            keepAliveEnabled="false"/>
    </binding>
  </customBinding>
</bindings>

In this example I’m also specifying binary message encoding for the SOAP endpoint because I’m certain that a .NET client will be calling the service, and this will result in a more compact wire representation of the message. (This is a very good practice in an all-.NET environment, but it is not taken advantage of as often as it should be.)

If you download the code for this post, you’ll see that it contains a FakeLoadBalancer project with a routing service that simulates a load balancer with SSL pass-through capability.  With a real load balancer, you could also get performance benefits from hardware-based acceleration for SSL.  The sample project contains the necessary binding configurations for both SOAP and REST style endpoints. Enjoy.

About Tony Sneed

Sr. Software Solutions Architect, Hilti Global Application Software
This entry was posted in Technical and tagged , , . Bookmark the permalink.

30 Responses to Building Scalable and Secure WCF Services

  1. Pingback: Distributed Weekly 160 — Scott Banwart's Blog

  2. Image with Keep-Alive disabled is not visible for me.

  3. Pingback: Building Scalable and Secure WCF Services - .NET Code Geeks

  4. Pingback: Hello world! | Ramesh's Blog

  5. Pingback: Experiencies NLB, WSDL, SSL, HTTPS « El TecnoBaúl de Kiquenet

  6. Ronald says:

    Hi, very helpful blogpost. I was wondering if the sourcecode is still available for download somewhere?
    Thanks!

  7. sadedil says:

    Hi Tony;

    When i try to this approach, i got an error message like this.
    The ‘CustomBinding’.’http://tempuri.org/’ binding for the ‘ISdlService’.’http://tempuri.org/’ contract is configured with an authentication mode that requires transport level integrity and confidentiality. However the transport cannot provide integrity and confidentiality.

    My Endpoint configuration:

    Do you have any comment.

  8. cvsayani says:

    Thank you for this post. I had the exact problem when migrating asmx to wcf using Username Password validation.

    I’ve implemented your solution and it works fine, but I have one issue.

    The Service EndPoint Address in the WSDL is generated as http instead of https.
    When I add Service Referernce, I manually change the endpoint configuration to https to get it working.

    Is there a way to configure in such a way that an https endpoint address is generated?

  9. antonio says:

    Your solution i very well but when try get WSDL from my solution i get this error:

    Policy export for such a binding is Error: Security policy export failed. The binding contains a TransportSecurityBindingElement but no transport binding element that implements ITransportTokenAssertionProvider. Policy export for such a binding is not supported. Make sure the transport binding element in the binding implements the ITransportTokenAssertionProvider interface. —-> System.InvalidOperationException: Security policy export failed. The binding contains a TransportSecurityBindingElement but no transport binding element that implements ITransportTokenAssertionProvider. Policy export for such a binding is not supported. Make sure the transport binding element in the binding implements the ITransportTokenAssertionProvider interface.
    at System.ServiceModel.Channels.TransportSecurityBindingElement.System.ServiceModel.Description.IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)

    There is any solution to generate WSDL ?

  10. antonio says:

    Yes I know and i just do it, but if I add MaxReceivedMessageSize in element “nonSslAuthTransport” on web.inf is not use I must had to constructor class of public NonSslAuthHttpTransportBindingElement (){
    MaxReceivedMessageSize = 2097152;
    MaxBufferPoolSize = 2097152;
    }

    😦 😦

  11. mikejr83 says:

    Tony, I ran into this problem a long time ago: https://serverfault.com/questions/80719/howto-wcf-service-https-binding-and-endpoint-configuration-in-iis-with-load-bala

    I’d like to update that Stack Overflow question with the correct answer. Another user answered with a link to your post. Since links don’t age well on the internet and that question does get a lot of views would you mind taking a some time and answering there? It would be awesome if you could also post your source to GitHub with a README.md!

    Thanks so much for this great info!

    Mike G.

  12. PUJA SEN says:

    I am facing the same issue. Can you please share the github link of this solution as majority of link seems not working.

  13. antonio says:

    hi ,
    sorry but link download it is broken can y upload again source code.

    tks a lot

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.