
May 21, 2014 13:24 by
Kenny
In this article i will explain about how to secure WCF Service on IIS. That is typically an easy requirement but I had a couple of restrictions:
- Only BasicHttpBinding could be used on the client-side
- Client credentials (username / password) must be sent and validated with each request
Windows Communication Foundation (WCF) is a framework for building service-oriented applications. Using WCF, you can send data as asynchronous messages from one service endpoint to another. A service endpoint can be part of a continuously available service hosted by IIS, or it can be a service hosted in an application. An endpoint can be a client of a service that requests data from a service endpoint. The messages can be as simple as a single character or word sent as XML, or as complex as a stream of binary data.

One option is to send and validate the credentials as parameters to each method. This method is usually seen as unacceptable because credentials are passed across the service as plain-text. Do you want your password sent over a web service as plain-text?
After doing some research, I came up with a solution that is largely a combination of two sources: configuring a WCF service on IIS with SSL and username authentication over BasicHttpBinding… I’ll do my best to consolidate both of those sources for simplicity.
Programming / Configuring the WCF Service
1. Open Visual Studio.
2. Create a new ‘WCF Service Application’ project.
3. Define a service interface:
using System.ServiceModel;
namespace Brett.Service
{
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
}
4. Implement your service interface. Our GetData method will return a string that shows the authenticated username and the integer value passed. If the current identity is not authenticated, we throw an exception (this is not our validation method, that is shown shortly):
using System;
using System.ServiceModel;
namespace Brett.Service
{
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("{0} entered: {1}", GetCurrentUserName(), value);
}
private string GetCurrentUserName()
{
var primaryIdentity = ServiceSecurityContext.Current.PrimaryIdentity;
if (primaryIdentity.IsAuthenticated)
{
return primaryIdentity.Name;
}
else
{
throw new Exception(@"User is not authenticated...");
}
}
}
}
5. Implement a custom credential validator. The class needs to extend the abstract class UserNamePasswordValidator . The important implementation detail here is that you want to throw a FaultException if the credentials are incorrect:
using System.IdentityModel.Selectors;
using System.ServiceModel;
namespace Brett.Service
{
public class CustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != @"hello")
{
throw new FaultException(@"User name must be 'hello'.");
}
}
}
}
6. Edit your web config file. This is probably the most tedious portion of the process. I’ll go ahead and attach the whole file here:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="Brett.Service.Service1"
behaviorConfiguration="Brett_Behavior">
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="Brett_BindingConfiguration"
contract="Brett.Service.IService1" />
<endpoint address="mex"
binding="mexHttpsBinding"
contract="IMetadataExchange" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="Brett_BindingConfiguration">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="Brett_Behavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="Brett.Service.CustomValidator,
Brett.Service"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
</configuration>
Some important things to note here… We expose two endpoints – one for the BasicHttpBinding and one for service metadata (mex). We are using BasicHttpBinding withTransportWithMessageCredential security (that further specifies a message credential type ofUserName ). The service credentials point to our CustomValidator . This is the absolute minimum needed for me to get the web service working – all elements were needed.
7. Publish the project somewhere local on your machine (right-click project, publish).
Configuring IIS for WCF Service with SSL
1. Follow instructions in this guide, until you reach ‘Configure WCF Service for HTTP Transport Security’ (don’t do that part). I would list these out myself, but I think the visuals provided in the link are very helpful.
2. I ended up having some file access issues with my application pool, so I ended up making my application pool run as an administrator identity.
Programming / Configuring the WCF Client
1. Open Visual Studio.
2. Create a new ‘Windows Console Application’ project.
3. Add a new service reference – use the location of your WCF Service that is hosted with IIS. The metadata for the service has been downloaded and an app.config file has been produced. We won’t use the app.config.
Program the console application to make a call to the service:
using Brett.Client.ServiceReference1;
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace Brett.Client
{
class Program
{
static void Main(string[] args)
{
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
var endpointAddress = new
EndpointAddress(@"https://localhost/WhateverYouNamedThis/Service1.svc");
using (var service = new Service1Client(binding, endpointAddress))
{
var loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = @"hello";
loginCredentials.UserName.Password = @"brett's password";
var defaultCredentials = service.Endpoint.Behaviors.Find<ClientCredentials>();
service.Endpoint.Behaviors.Remove(defaultCredentials); //remove default ones
service.Endpoint.Behaviors.Add(loginCredentials); //add required ones
var data = service.GetData(100);
Console.WriteLine(data);
Console.ReadLine();
}
}
}
}