UK Consulting Links
UK Consulting Blogs
Sometimes, Windows Communication Foundation (WCF) can be tough going. Whilst things will often work straight out of the box, customisation can quickly get complicated. Equally though, the solution can sometimes turn out to be pretty simple but it is the journey to get there that is difficult.
Consider the following scenario:
In this configuration a WCF service was secured by Forefront Threat Management Gateway (TMG) 2010 so that the client must negotiate mutual SSL authentication with TMG before the message can be forwarded on to the service.
From the point of view of the service this was simple message level security because transport security was being taken care of by TMG. The service configuration was therefore the following:
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="myServiceBehaviour"> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFService.CustomUserNameValidator, WCFService" /> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="basicBinding"> <security mode="TransportWithMessageCredential"> <message clientCredentialType="UserName"/> </security> </binding> </basicHttpBinding> </bindings> <services> <service name="WCFService.Service1" behaviorConfiguration="myServiceBehaviour"> <endpoint address="https://www.myservice.com.local/service1.svc" binding="basicHttpBinding" bindingConfiguration="basicBinding" contract="WCFService.IService1" /> </service> </services></system.serviceModel>
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1"> <security mode="TransportWithMessageCredential"> <transport clientCredentialType="Certificate" /> <message clientCredentialType="UserName" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://www.myservice.com/Service1.svc" behaviorConfiguration="myEndpointBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Client.IService1" name="BasicHttpBinding_IService1" /> </client> <behaviors> <endpointBehaviors> <behavior name="myEndpointBehaviour"> <clientCredentials> <clientCertificate storeName="My" storeLocation="CurrentUser" findValue="CN=WCF client cert 2" /> </clientCredentials> </behavior> </endpointBehaviors> </behaviors></system.serviceModel>
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1"> <security mode="Transport"> <transport clientCredentialType="Certificate" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://www.myservice.com/Service1.svc" behaviorConfiguration="myEndpointBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Client.IService1" name="BasicHttpBinding_IService1" /> </client> <behaviors> <endpointBehaviors> <behavior name="myEndpointBehaviour"> <clientCredentials> <clientCertificate storeName="My" storeLocation="CurrentUser" findValue="CN=WCF client cert 2" /> </clientCredentials> </behavior> </endpointBehaviors> </behaviors></system.serviceModel>
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1"> <security mode="TransportWithMessageCredential"> <transport clientCredentialType="Certificate" /> <message clientCredentialType="UserName" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://www.myservice.com/Service1.svc" behaviorConfiguration="myEndpointBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Client.IService1" name="BasicHttpBinding_IService1" /> </client> <extensions> <behaviorExtensions> <add name="sendClientCertificate" type="WCFClient.SendClientCertificateBehaviorElement, WCFClient"/> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="myEndpointBehaviour"> <clientCredentials> <clientCertificate storeName="My" storeLocation="CurrentUser" findValue="CN=WCF client cert 2" /> </clientCredentials> <sendClientCertificate /> </behavior> </endpointBehaviors> </behaviors></system.serviceModel>
namespace WCFClient{ using System; using System.ServiceModel.Channels; using System.ServiceModel.Description; public class SendClientCertificateBehavior : Attribute, IEndpointBehavior { #region IEndpointBehavior Members public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { Binding bbinding = endpoint.Binding; BindingElementCollection bec = bbinding.CreateBindingElements(); TransportSecurityBindingElement tsp = bec.Find<TransportSecurityBindingElement>(); HttpsTransportBindingElement httpsBinding = bec.Find<HttpsTransportBindingElement>(); TextMessageEncodingBindingElement encoding = bec.Find<TextMessageEncodingBindingElement>(); httpsBinding.RequireClientCertificate = true; CustomBinding b = new CustomBinding(tsp, encoding, httpsBinding); endpoint.Binding = b; } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { throw new NotImplementedException(); } public void Validate(ServiceEndpoint endpoint) { } #endregion }}
Written by Bradley Cotier
Hi Bradley,
I'm facing similar issue with WCF Self hosted service in TransportWithMessageCredential mode. In my case, When i mention client credential type as Certificate for both Transport and Message, the client credential gets passed to the service and mutual authentication works. But my need is to have windows authorization @ message level. Hence for client credential type certificate @ transport and windows as message, i could not get the client credential passed to service. It doesnt throw exception either. When i removed client cert CA on server side, I could still make call without exception/errors. I also mentioned the requireclientcetificate = true via behavior on client side as mentioned in the blog. Still no luck. Any suggestions would be of great help.
-Aswin
Well i found the reason why it wasnt behaving the expected way. I created a custom binding with behavior mentioned in the blog and used it across both client and server side & it worked.
Could either of you see the web service from outside the fire wall prior to this?
I have a similar problem, but our 3rd party sees the service description page fine. When the test app calls the authentication method of the WCF service, it returns the service description page as the response, instead of the small xml file it should be passing...
Hi
Prior to the firewall being in place, the service was configured with message security only. There was a desire to only allow vetted callers to access the service but that the burden should not be placed on the web service server itself, but rather at the firewall layer.
As you are not getting a security exception it sounds like you have some other problem with your client config. I would suggest that you get the client/service working without a firewall in place. You then have a good foundation to increment upon.
Thanks
Brad