Client certificates for HTTPS WCF services

I spent some time today figuring out how to create a WCF client for a service that requires client-side authentication via certificate. In the end, the fix was simple, but it took me a while to get there so I figured I'd share it out in the hope that future searches (like mine) will lead someone here and to the solution. There are a two parts to this - one on the service side, the other on the client side. I'll assume you already have the required certificate installed on the client machine.

Server side

You need to allow the service to advertise its capabilities first. At least for my service, when it was secured with client-side certifications as a requirement, this was disabled. You need to do two things to remedy this:

  • Add a new endpoint that provides configuration information, in my case a MEX (Metadata EXchange) endpoint.
  • Enabling retrieval of this metadata as a behaviour of my service. 

Here's what my web.config looked like after I made the modifications. Changes in red. Bits in blue are where you need to fill in details of your application.

<configuration>

 <system.webServer>  <security>   <access sslFlags="Ssl, SslNegotiateCert, SslRequireCert"/>  </security> </system.webServer>

 <system.serviceModel>  <services>   <service name="MyServiceName" behaviorConfiguration="MyServiceBehavior">    <endpoint binding="wsHttpBinding" bindingConfiguration="wsHttpSecureBinding" contract="MyServiceInterface"/>     <endpoint contract="IMetadataExchange" bindingConfiguration="wsHttpSecureBinding" binding="wsHttpBinding" address="mex" />    </service>  </services>

  <behaviors>   <serviceBehaviors>    <behavior name="MyServiceBehavior">     <serviceCredentials>      <clientCertificate>       <authentication certificateValidationMode="Custom" customCertificateValidatorType="MyCustomValidator, MyCustomValidatorAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef"/>      </clientCertificate>     </serviceCredentials>      <serviceMetadata httpGetEnabled="true" />

    </behavior>   </serviceBehaviors>  </behaviors>

  <bindings>   <wsHttpBinding>    <binding name="wsHttpSecureBinding">     <security mode="Transport">      <transport clientCredentialType="Certificate"/>     </security>    </binding>   </wsHttpBinding>  </bindings> </system.serviceModel></configuration>

 

Client side

I use scvutil.exe to generate my WCF clients, and I wanted to continue doing it this way. It turns out that svcutil will read a config file placed alongside it, and this is how you specify which certificates to use when hitting the service. Without this, IIS will reject the request and you won't be able to get the service description to generate a client. Just create a svcutil.exe.config file next to svcutil.exe (not in your current working directory) and the configuration should take effect. Alternatively, you can pass in a config file to svcutil by calling svcutil.exe https://example.com/myService.svc /config:myconfig.config

 

Here's what my svcutil.exe.config looked like. You'll probably need to change the parts in blue to suit your certificate and its location.

<configuration>  <system.serviceModel>

    <client>      <endpoint behaviorConfiguration="ClientCertificateBehavior" binding="wsHttpBinding" bindingConfiguration="MyBinding" contract="IMetadataExchange" />    </client>

    <behaviors>      <endpointBehaviors>        <behavior name="ClientCertificateBehavior">          <clientCredentials>      <clientCertificate findValue="0123456789abcdef0123456789abcdef01234567"       x509FindType="FindByThumbprint" storeLocation="CurrentUser"       storeName="My"/>          </clientCredentials>        </behavior>      </endpointBehaviors>    </behaviors>

 <bindings>      <wsHttpBinding>        <binding name="MyBinding">          <security mode="Transport">            <transport clientCredentialType="Certificate" />          </security>        </binding>      </wsHttpBinding>    </bindings>

  </system.serviceModel></configuration>

There are a number of different ways to define which certificate to use, but my preference is by thumbprint as there is certainly no ambiguity then.

Now when you run svcutil, it will look up the specified certificate and use it when attempting to communicate with the IMetadataExchange service to retrieve your service description.

 

Conclusion

Hopefully you're now up and running! I can also recommend How to: Use Certificate Authentication and Transport Security in WCF Calling from Windows Forms for a more in-depth and end-to-end guide for how to achieve this, but I hope this guide has been sufficiently terse while giving you the essential information. Please let me know in comments if you need any help.

-Stephen

Comments

  • Anonymous
    April 08, 2014
    It was a good explanation and clear with couple of exceptions:
  1. The custom certificate validator is not explaned.  Nor whether this can be done without a custom certificate validator.
  2. You set httpGetEnabled to 'true', but did not set httpsGetEnabled which I believe defaults to 'false'.  Questionable with an SSL website. However, it doesn't work for me.  If I configure httpGetEnabled as true I am unable to connect to the website with a browser and existing clients on this service fail. With httpGetEnabled set to 'false' and httpsGetEnabled set to 'true'. I get the following error: The HTTP request was forbidden with client authentication scheme 'Anonymous'.  The remote server returned an error: (403) Forbidden.
  • Anonymous
    May 13, 2014
    I need to be able to add a service reference secured with https access to an external wsdl to a windows form   I receive a 403 error in the Add Service Reference dialog in Visual Studio 2012.  I am coding in Visual Basic.  Can you provide a technique - possibly like the side-by-side technique for SVCUtil?  I tried cloning the config code in this blog into the VS devenv.exe.config file and that did not do the job - even though VS executed recompilation of my project without any apparent errors.