Tools used
- Pluralsight Self-Cert Tool. This tool is provided by Pluralsight to create and install certificates.
- WinHttpCertCfg.exe.
Windows HTTP Services Certificate Configuration Tool is a command line
tool to grant specific users read right access on a certificate's
private key file.
- Environment: Visual Studio 2010, and IIS7 or above.
Contents
- Introduction
- Creating the service
- Configuring the service
- Configuring IIS and publishing the website
- Installing the server side certificate
- Setting up the client
Windows Communication Foundation comes with a rich set of security
features such as transport level message and transport with message;
each security type has its own advantages and overheads as well. My
application has lots of diverse clients used to connect with the
service, and they have to be authenticated from the database, so the
best possible solution is message level security using custom username -
password authentication. After digging in to the net, I found pieces of
information, and with some effort, I implemented a concrete solution
which I am hoping is helpful for others.
The solution is created using VS2010, and contains three projects:
the WCF Service, the website, and the desktop application which is the
client application.
The WCF Service just contains a function
GetServertime():
Collapse |
Copy Code
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetServertime();
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Service1 : IService1
{
public string GetServertime()
{
return DateTime.Now.ToString();
}
}
We create a class and name it
UserNamePassValidator. We the implement this code in it:
Collapse |
Copy Code
using System;
using System.ServiceModel;
namespace CustomUsernamePasswordAuth.Service
{
class UserNamePassValidator :
System.IdentityModel.Selectors.UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if( userName==null || password==null)
{
throw new ArgumentNullException();
}
if (!(userName == "fayaz" && password == "soomro") )
{
throw new FaultException("Incorrect Username or Password");
}
}
}
}
This class must be derived from
System.IdentityModel.Selectors.UserNamePasswordValidator and override the
Validate method. And to validate the user, use any data source; in this example, we will use a hard coded value.
Creating the web application
Add a reference to the service in the web application. Add a text file and rename it to
UserNamePassService.svc, and add the following line of code:
Collapse |
Copy Code
<%@ ServiceHost Language="C#" Debug="true"
Service="CustomUsernamePasswordAuth.Service.Service1" %>
Modify the
web.config and add following lines in it.
Add a service behavior and name it
Behavior1. Enable the service meta data by adding
<serviceMetadata httpGetEnabled="true"/>
so that when we add a service reference into the client application, it
fetched the information and creates the proxy classes for us. And the
essential part is the service certificate. Certificate creation will be
covered in a later section, but now, we have to remember the certificate
settings.
FindValye="MyWebSite" will be the subject for the certificate
CN=MyWebSite, and you can change this value to your domain name or project name.
Set the
usernamepasswordvalidation mode to custom, and
customUsernameapsswordValidator has to be specify the custom validation class and namespace.
Collapse |
Copy Code
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Behavior1">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="MyWebSite"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="CustomUsernamePasswordAuth.
Service.UserNamePassValidator, CustomUsernamePasswordAuth.Service" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
Set up the binding configuration as shown below. Name it
Binding1 and set the security mode as
Message and
clientCredentialType as "
username".
Collapse |
Copy Code
<bindings>
<wsHttpBinding>
<binding name="Binding1">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
Now we will set up the service endpoint. There are two endpoints:
wsHttp endpoint, and Mex end point for metadata exchange. The base
address is
http://localhost/. The fully qualified service address will be
http://localhost/UserNamePassService.svc.
Collapse |
Copy Code
<services>
<service behaviorConfiguration="Behavior1"
name="CustomUsernamePasswordAuth.Service.Service1">
<endpoint address="" binding="wsHttpBinding"
bindingConfiguration="Binding1"
contract="CustomUsernamePasswordAuth.Service.IService1" />
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
Note: if the website is going to be hosted on a
specific port in IIS, as in this example, we have hosted the website in
IIS on port 83,
http://localhost:83/UserNamePassService.svc, we don't need to change the port in the configuration file and leave the
baseAddress as "http://localhost/".
Open IIS Manager. Right click Sites and Add Website. Name it as
WebSite, set Application pool to DeafaultAppPool, and select the physical path and set port to 83. As shown below:
Set the DefaultAppPool Framework version to 4.0.
Publish Site to IIS
Right click the website project in Solution Explorer and publish it.
Select Publish method as File system, and Target location as
http://localhost:83, as shown in the figure below:
Browse the site. Open Internet Explorer and type
http://localhost:83/UserNamePassService.svc. You will see the error that X.509 could not be found.
Download the Pluralsight SelfCert from the link given at the
beginning of the article. Run the tool as Administrator; otherwise, it
will crash.
Configure the settings to install the certificate; refer the screen below.
After making the required changes, click the Save button and then you will see the screen below:
After the installation of the certificate, browse the site again, but
this time, you should see a different error as shown in the screen
below:
This error means that the default application pool does not have
access rights to the certificate's private key, so now, we have to give
read access to the default application pool to do this.
Download
WinHttpCertCfg.exe from the link given at the
beginning of the article. This tool is a command line tool. After
installing the tool, run the following command on the command prompt as
Administrator.
Collapse |
Copy Code
C:\Program Files (x86)\Windows Resource Kits\Tools>winhttpcertcfg
-g -c LOCAL_MACHINE\My -s MyWebSite -a DefaultAppPool
After running the command, you will see the screen like this:
Now browse the site again. And verify the service, it should be up.
The client application is the desktop application, and just contains the address textbox and the button to get the server time.
Now add the service reference to the project:
Add the code to the
Button_click event:
Collapse |
Copy Code
private void button1_Click(object sender, EventArgs e)
{
string time = "";
Service1Client c = new Service1Client();
c.ClientCredentials.UserName.UserName = "fayaz";
c.ClientCredentials.UserName.Password = "soomro";
c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
time = c.GetServertime();
MessageBox.Show(time);
var ServiceendPoint = new EndpointAddress(new Uri(txtServiceAddress.Text),
EndpointIdentity.CreateDnsIdentity("MyWebSite"));
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
var result = new Service1Client(binding, ServiceendPoint);
result.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
result.ClientCredentials.UserName.UserName = "fayaz";
result.ClientCredentials.UserName.Password = "soomro";
time = result.GetServertime();
MessageBox.Show(time);
}
Run the application:
Running the client from another PC to make sure everything works fine:
Conclusion
I 'm sure this project will be useful for developers who want to
implement custom security. I tried my best to describe each step with a
screenshot. I hope you've enjoyed this article. If you like this
article, please let me know :). If you have any questions, please feel
free to contact me at fayaziiui@gmail.com