This took me days of fiddling, but when I eventually sussed it was quite easy! WCF services have to be secured, and you have have a few options;
- Message security
The messages are encrypted
- Transport security
The connection is secured (SSL)
- Message & Transport
Both of the above
I want to use message based security (so no SSL) and I wanted AspNet Membership (as used in forms authentication by default) to manage the users allowed to use the service.
The key to message based security, is an X.509 certificate installed on the server. You can buy a cert from a trusted certificate authority (like Verisign) or you can make your own self signed cert. I went for the self signed certificate option, using the awesome SelfCert tool on the pluralsite blog as this is a system used internally- for an externally facing public service you’de probably want a proper certificate from a recognised certificate authority.
Drop the tool on your server, and fire it up- give it a common name (CN=) of whatever you want- for the sake of the example we’ll call it MyCert. I put the certificate in the “My” store.
Once that’s done you need to make sure the user account of whatever process hosts your WCF service has access to read the certificate. In this example, I’m running the WCF service in a web site hosted by IIS 7, in an app pool called “WCFDemo.shawson.co.uk”- so the identity is “IIS APPPool\wcfdemo.shawson.co.uk”. To assign permissions you can use another handy tool called winhttpcertcfg – once installed on your server, fire up the command line and run;
C:\Program Files (x86)\Windows Resource Kits\Tools>winhttpcertcfg -g -c LOCAL_MACHINE\My -s MyCert -a WCFDemo.shawson.co.uk
This will come back and tell you permissions have been assigned and everything is good. Ok so lets build the service.
I created a new MVC3 project (this doesn’t really matter) and added a WCF service to the project called TestService.svc. This had a single method called Hello which accepted a string “name” and returned a string. I setup the aspnet membership and added a single account called tester with a password of tester1.
Once thats all sorted, the critical bit is in the web config, which is where most of the WCF magic happens- so the server side config for the service looks like this;
<system.serviceModel> <services> <!-- the service end point- this ties everything together for the service --> <service name="WCFCertificateTest.Services.TestService"> <endpoint address="/" binding="wsHttpBinding" bindingConfiguration="MessageSecurity" contract="WCFCertificateTest.Services.ITestService" /> </service> </services> <bindings> <wsHttpBinding> <binding name="MessageSecurity" > <!-- specifies we cant message, not transport security --> <security mode="Message"> <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name=""> <serviceMetadata httpGetEnabled="true"/> <serviceCredentials> <!-- this is the bit which tells WCF to use the ASPNet sql membership --> <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="AspNetSqlMembershipProvider" /> <!-- this next bit tells the server which certificate we should be using the encrypt the messages --> <serviceCertificate findValue="MyCert" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/> </system.serviceModel>
The server side is now ready. Make sure you run this on the same machine we created the certificate on earlier- fire it up!
Now for the client– this is the really easy bit.
For the purpose of this demo, I created a windows console app. Add the service reference.
Enter the url for your service, and select it, then enter a namespace for it (I chose TestService) then click ok. This will add the reference to your client app and write a bunch of stuff into the config. In my demo console app I got this;
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_ITestService"> <security> <message clientCredentialType="UserName" /> </security> </binding> </wsHttpBinding> </bindings> <client> <endpoint address="http://wcfdemo.shawson.co.uk/Services/TestService.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITestService" contract="TestService.ITestService" name="WSHttpBinding_ITestService"> <identity> <certificate encodedValue="AwAAAAEAAAAUAAAAyb+3RrSzF0j7Lm7TONYkIpPPJosgAAAAAQAAAK4EAAAwggSqMIICkqADAgECAhAwLsfZzjx....QGqKDw4P1sScwBANFBjdRSrKzNyVR7b5gDU3geiPsXvS3an6kxZ" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
The critical part of this is the big string of jibberish in the identity certificate. When you add the reference it goes to the server and grabs this string, which is unique to the certificate we created earlier. If you are running this from local, and later deploy to a production server with a new certificate (even if it has the same common name), make sure you update the service reference, as this string will be different!
We can now make the call from the application- The asp net membership cerdentials are passed over using the ClientCredential.Username element (lines 3-4). Because we’re using a self signed certificate not for a trusted root authority we add a line (line 5 in the example below) to change how dot net validates the certificate- basically telling it not to worry about verifying the certificate.
TestService.TestServiceClient client = new TestService.TestServiceClient(); client.ClientCredentials.UserName.UserName = "tester"; client.ClientCredentials.UserName.Password = "tester1"; client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None; Console.WriteLine(client.Hello("Shaw")); Console.ReadLine();