How to fake Transport Security in WCF

Published on April 14, 2009 by Toran Billups

I have spent the last 18 months in and out of the WCF stack but until recently I had no reason to write a custom binding. This has been in part because WCF out of the box offers enough configuration options that I did not need to get my hands dirty. This was a big reason I took a step toward WCF and a step away from ASMX in 2007. Especially when it comes to SOAP and the usual WS* mess everyone in the web service world loves so much. With WCF you get all the SOAP 1.1 / 1.2 work done for you, among a ton of other messaging configurations. And if you are working in or around the enterprise like I do, you can also tap into the membership provider to leverage role based access with a few simple attributes.

In my first few 'demo-ware' applications to get hype around WCF I used a simple WsHttpBinding coupled with the membership provider via service behavior.

 1 <system.serviceModel>
 2 		<services>
 3 			<service name='SampleApplicationWCF.Library.ProductService' behaviorConfiguration='NorthwindBehavior'>
 4 				<endpoint address='' name='wsHttpProductService' contract='SampleApplicationWCF.Library.IProductService' binding='wsHttpBinding' bindingConfiguration='wsHttp'/>
 5 			</service>
 6 		</services>
 7 		<bindings>
 8 			<wsHttpBinding>
 9 				<binding name='wsHttp'>
10 					<security mode='TransportWithMessageCredential'>
11 						<transport/>
12 						<message clientCredentialType='UserName' negotiateServiceCredential='false' establishSecurityContext='true'/>
13 					</security>
14 				</binding>
15 			</wsHttpBinding>
16 		</bindings>
17 		<behaviors>
18 			<serviceBehaviors>
19 				<behavior name='NorthwindBehavior'>
20 					<serviceMetadata httpGetEnabled='true'/>
21 					<serviceAuthorization principalPermissionMode='UseAspNetRoles'/>
22 					<serviceCredentials>
23 						<userNameAuthentication userNamePasswordValidationMode='MembershipProvider'/>
24 					</serviceCredentials>
25 				</behavior>
26 			</serviceBehaviors>
27 		</behaviors>
28 	</system.serviceModel>

But with these samples being just that, they never got implemented in our production environment. But late last year another developer came to me with a project that had a requirement to interop with someone outside of our network. I took on a mentor role as I thought I knew it all ... and we got a working solution ready for a migration to staging. And to my surprise I found that WCF required SSL be installed on the IIS server(s) when you use the security mode TransportWithMessageCredential inside a WsHttpBinding. This makes sense as you want to ensure SSL is setup when you start throwing around usernames and passwords.

In my case it turns out I had not done my homework with regards to our infrastructure because the production enviornment had a proxy that sits in front of the load balanced IIS farm. And it was on this proxy that our SSL certs are installed (instead of on the IIS box themselves). Here is where I ran into my dilemma ...

We needed a solution that would allow the WSDL to be pulled from an individual IIS server via the farm, yet ensure usernames were not being thrown around in plain text. The solution came from a cry of desperation on stackoverflow after I had exhausted almost 2 weeks worth of R&D with almost no results.

The only answer I got on stackoverflow pointed me to this post

And from this post I was pointed to another

Armed with a new set of information it looked as if I could 'fake' the Transport so the WCF service would 'function' like the normal WsHttpBinding even if the IIS server itself did not have any sort of SSL cert installed.

I could have pointed to the posts above and called it quits, but what I found is that they did not seem to be 100% complete. This is where I come in.

The first class that you need to create will represent a custom binding element for our 'faked' transport binding.

 1 namespace App.BindingExtenions {
 2     public class HttpsViaProxyTransportBindingElement : HttpTransportBindingElement, ITransportTokenAssertionProvider {
 3         public override T GetProperty<T>(BindingContext context) {
 4             if (typeof(T) == typeof(ISecurityCapabilities))
 5                 return (T)(ISecurityCapabilities)new AutoSecuredHttpSecurityCapabilities();
 7             return base.GetProperty<T>(context);
 8         }
10         public override BindingElement Clone() {
11             return new HttpsViaProxyTransportBindingElement();
12         }
14         public System.Xml.XmlElement GetTransportTokenAssertion() {
15             return null;
16         }
17     }
18 }

Next you will need to create a custom implementation of the ISecurityCapabilities interface to satisfy the dependency found in the above.

 1 namespace App.BindingExtenions
 2 {
 3     public class AutoSecuredHttpSecurityCapabilities : ISecurityCapabilities
 4     {                
 5         public ProtectionLevel SupportedRequestProtectionLevel
 6         {
 7             get { return ProtectionLevel.EncryptAndSign; }
 8         }
10         public ProtectionLevel SupportedResponseProtectionLevel
11         {
12             get { return ProtectionLevel.EncryptAndSign; }
13         }
15         public bool SupportsClientAuthentication
16         {
17             get { return false; }
18         }
20         public bool SupportsClientWindowsIdentity
21         {
22             get { return false; }
23         }
25         public bool SupportsServerAuthentication
26         {
27             get { return true; }
28         }
30     }
31 }

And finally you will need to create one last class to represent the actual transport element

 1 namespace App.BindingExtenions {
 2     public class HttpsViaProxyTransportElement : HttpTransportElement {
 4         public override Type BindingElementType {
 5             get {
 6                 return typeof(HttpsViaProxyTransportBindingElement);
 7             }
 8         }
10         protected override TransportBindingElement CreateDefaultBindingElement() {
11             return new HttpsViaProxyTransportBindingElement();
12         }
14     }
15 }

Now you have the binding Element Extension needed for this custom WCF Binding.

After you have the above compile friendly, add a reference to the assembly from your WCF Service project.

Below is a very simple example of how to use this custom binding in your config file

 1 <system.serviceModel>
 2     <services>
 3       <service name='SampleApplicationWCF.Library.Service.ProductService' behaviorConfiguration='NorthwindBehavior'>
 4         <endpoint address='' name='wsHttpProductService' contract='SampleApplicationWCF.Library.Service.Interfaces.IProductService' binding='customBinding' bindingConfiguration='UserNamePasswordSecured'/>
 5       </service>
 6     </services>
 7     <extensions>
 8       <bindingElementExtensions>
 9         <add name='httpsViaProxyTransport' type='App.BindingExtenions.HttpsViaProxyTransportElement, ClearTextBinding'/>
10       </bindingElementExtensions>
11     </extensions>
12     <bindings>
13       <customBinding>
14         <binding name='UserNamePasswordSecured'>
15           <textMessageEncoding />
16           <security authenticationMode='UserNameOverTransport' />
17           <httpsViaProxyTransport />
18         </binding>
19       </customBinding>
20     </bindings>
21     <behaviors>
22       <serviceBehaviors>
23         <behavior name='NorthwindBehavior'>
24           <serviceDebug includeExceptionDetailInFaults='true'/>
25           <serviceMetadata httpGetEnabled='true'/>
26           <serviceAuthorization principalPermissionMode='UseAspNetRoles'/>
27           <serviceCredentials>
28             <userNameAuthentication userNamePasswordValidationMode='MembershipProvider'/>
29           </serviceCredentials>
30         </behavior>
31       </serviceBehaviors>
32     </behaviors>
33   </system.serviceModel>

And finally - the best part is that you do not need to add this custom assembly to your client app. Instead your client app can keep the config settings to reflect a normal WsHttpBinding. The only item you need to confirm is set in your client binding is the security section (shown in the below client config)

 1 <system.serviceModel>
 2     <bindings>
 3       <wsHttpBinding>
 4         <binding name='ServiceCustomBinding' receiveTimeout='12:59:59' >
 5           <reliableSession ordered='false' inactivityTimeout='12:58:59'/>
 6           <security mode='TransportWithMessageCredential'>
 7             <transport clientCredentialType='None'/>
 8             <message clientCredentialType='UserName' establishSecurityContext='false' />
 9           </security>
10         </binding>
11       </wsHttpBinding>
12     </bindings>
13     <client>
14       <endpoint address='https://proxyserver/app/productservice.svc'
15           binding='wsHttpBinding' bindingConfiguration='ServiceCustomBinding'
16           contract='ProductService.IProductService' name='wsHttpProductService' />
17     </client>
18   </system.serviceModel>

The final tweak needed is at the service level (in your WCF project) to avoid any Address Mismatch error you might get

 1 namespace Service
 2     {
 3         [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
 4         public class ProductService : IProductService
 5         {
 6            public IEnumerable<Product> GetProductCollection()
 7            {
 8                return null;
 9            }
11        }
12    }

Now when my client app connects to the web service via the proxy (https) I actually connect to the IIS server w/out any SSL cert installed. (A-Ha! We have faked the transport with message creds!)

If you want to investigate further, download the source here

Update:If you download the sample above keep in mind that I only included the class library needed to produce the assembly for the custom binding, not a sample WCF service project or sample client project. The 2 configuration files included are valid examples of how one might use this custom binding to setup communication between a WCF SOAP web service and a client application.