Working with Certificates – Part II, Securing a WCF service using custom username and password authentication
One of the many common security scenarios when programming WCF, is using custom username and password authentication.
The custom authentication part is no big hassle in itself, but as a security precaution, WCF refuses to send usernames and passwords in clear-text, instead requiring that the communication be encrypted in some way.
Attempting to use custom authentication without first encrypting the communication will cause an exception, usually this one:
System.InvalidOperationException: The service certificate is not provided. Specify a service certificate in ServiceCredentials.
There are several ways to secure the communications in WCF, which basically boil down to using either Windows-specific security (Windows username and password), federated security (involving a third party) such as Windows Live Id, or using certificates.
See this article on MSDN for more information.
Since we want to use a custom username and password, that rules out the Windows-specific security, which obviously has its own authentication mechanism (either Kerberos or NTLM). You could implement your own federated security provider, but most who have opted for this solution have given up half-way due to the amount of code you need to write. Don’t get me wrong, federated security is awesome, when you need federated security and nothing else will do.
But since we just want a simple username and password validation, we’ll go for the certificate solution.
You can do this in two ways. You can either hook up your service to use SSL, either via HTTPS or a TCP endpoint, or you can load the certificate into your service and use message level security.
For simplicity, we’ll use message level security over HTTP. This way we don’t need to involve HTTPS in our demonstration.
In my previous article, I briefly discussed how certificates work and how you can set up your own Root Certificate Authority and issue your own certificates, so we’ll skip the part where you create a certificate and jump right to the part where you start using it.
For this article, I’ve created a self-signed SSL server certificate with the subject name localhost. That will suffice to test the code locally. If you want to put the service on another computer than the client, you’ll need to issue a certificate with a subject name equal to the DNS name of the computer where the service is hosted. Such as MyOtherMachine, or www.myserver.local or something like that.
Once you have your certificate created, you need to import the certificate with the private key, since it will be used to decrypt communications from the client. Any format that Windows understands will do, such as a personal information exchange file (.pfx) or PKCS #12 (.p12).
Import the certificate along with the private key to the Trusted People store. To do this, just double click the certificate file and follow the “Import Certificate” wizard until you are prompted to specify which store you want to put it in. Click the “Browse” button and select Trusted People. We want it in the Trust People store so we can tell WCF to use Peer Trust (explained in the code below).
The first thing we’ll do is create a blank solution and add two projects to it, a WCF Service Library and a Console Application.
You should have something like this:
Next, add a service reference to the service from the client. Then, add some testing code to the client:
static void Main(string[] args)
{
using (SecureServiceClient proxy =
new SecureServiceClient())
{
Console.WriteLine(proxy.GetData(15));
}
Console.WriteLine();
Console.Write("Press any key to exit. . .");
Console.ReadKey();
}
Run it once and make sure it works without any security, before complicating things.
Then, we add the class that will validate the custom username and password to the service:
public class SampleAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName,
string password)
{
// Validate arguments
if (userName == null)
throw new ArgumentNullException("userName");
if (password == null)
throw new ArgumentNullException("password");
// Validate username and password
if (userName != "test1" || password != "1tset")
{
throw new SecurityTokenException(
"Invalid username or password.");
}
}
}
And all we need to do now is configure the service to use a certificate, hook up the new validation class, and provide the username and password in the client.
First off is the app.config file for the service:
<services>
<service behaviorConfiguration="SampleX509Behavior"
name="Hexadecimal.SampleService.SecureService">
<endpoint address="" binding="wsHttpBinding"
bindingConfiguration="SampleX509Binding"
contract="Hexadecimal.SampleService.ISecureService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/Design_Time_Addresses/hexadecimal/Samples/SecureService/" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="SampleX509Binding">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="SampleX509Behavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
<serviceCredentials>
<serviceCertificate findValue="localhost"
storeLocation="CurrentUser"
storeName="TrustedPeople"
x509FindType="FindBySubjectName" />
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="Hexadecimal.SampleService.SampleAuthenticator, SampleService" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
As you can see, it’s fairly basic. The only additions are the binding which specifies message security mode and the behavior which indicates that we want to use custom username and password validation, and provides a reference to the class that will perform the actual validation, as well as provide information on how WCF will find the certificate.
Basically, we have told WCF to go look for our certificate in the Trusted People store, specific to the current user (which the service process runs as), and search for a certificate that has “localhost” as subject name. You will run into exceptions if WCF finds more than one certificate which fulfill the search criteria, which can sometimes happen if you are experimenting a lot with certificates and importing them left and right.
Note that the subject name and the <dns> identity of the service must match! If they don’t you will get a very non-descriptive exception on the client, since it’s the client that requires this, not the service, unless you turn off certificate validation on the client side.
Next, we refresh the service reference on the client, so that the client’s app.config file looks something like this:
(I tidied it up a bit for clarity)
<bindings>
<wsHttpBinding>
<binding name="SampleBinding">
<security mode="Message">
<message clientCredentialType="UserName"
negotiateServiceCredential="true"
algorithmSuite="Default"
establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8731/Design_Time_Addresses/hexadecimal/Samples/SecureService/"
binding="wsHttpBinding"
bindingConfiguration="SampleBinding"
contract="SampleService.ISecureService"
name="WSHttpBinding_ISecureService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
Ok, so we’ve told both the client and the service to use a custom username and password for authentication and a certificate to secure the confidentiality of the communications.
Next, we just need to add the actual username and password to the client, and tell WCF not to be so strict when checking our home-grown test certificate:
static void Main(string[] args)
{
using (SecureServiceClient proxy = new SecureServiceClient())
{
// Provide authentication details
proxy.ClientCredentials.UserName.UserName = "test1";
proxy.ClientCredentials.UserName.Password = "1tset";
// Using peer trust will make WCF accept the
// certificate from the service if the certificate
// is also installed on the client computer in
// the Trusted People store (peer = trusted person)
// Even if it isn't signed by a valid Root CA
proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.PeerTrust;
// Dot not go online and check if the SSL certificate
// has been revoked since our demo certificate does
// not have a valid CRL pointer.
proxy.ClientCredentials.ServiceCertificate.Authentication.RevocationMode =
X509RevocationMode.Offline;
// Make a service call
Console.WriteLine(proxy.GetData(15));
}
Console.WriteLine();
Console.Write("Press any key to exit. . .");
Console.ReadKey();
}
That’s it! Run it and see for yourself!
If you have a certificate which can be validated, you can remove the two lines of code which lowers the certificate validation requirements of WCF.
Typically, this is what a service would look like when I am developing and testing, and then for actual release, I’ll reconfigure it to use a real certificate with stricter validation.

