HOW TO: Use a Custom Membership Provider, Custom Membership User and Role-Provider with a WCF Service hosted inside a Windows Service.

This HOW TO shows how to use your custom membership and role provider for SOAP Header Authentication in a WCF Service that is hosted inside a Windows Service. It requires some basic knowledge of developing Windows Services, WCF Applications and Membership Providers. I won’t explain how to create a solution or project in Visual Studio. I will use Visual C# in this HOW TO, but it can be easily adapted to use Visual Basic.

It will not discuss the implementation of a custom membership and/or role provider. Please create and test your custom providers first. For more information on how to create a custom provider and samples of such, please check the following MSDN articles;

Implementing a Membership Provider
How to: Sample Membership Provider Implementation
How to: Implement a Custom Membership User
Implementing a Role Provider
How to: Sample Role-Provider Implementation

The sample Membership Provider provided in the demo code has been modified to work with a Microsoft SQL Database. On lines 1008, 1170 and 1267 of the DemoMembershipProvider.cs file, the “= False’” statements have been replaced with “= 0” statements.

In order to use username/password authentication in a WCF Service, you are required to secure the transport or message (by means of a X509 certificate). This HOW TO does not cover the creation and installation of certificates. But the demo code, that uses Message Security, will work if you follow the instructions on the page at the second link in this list;

How to: Create Temporary Certificates for Use During Development
How to: Create and Install Temporary Certificates in WCF for Message Security During Development
How to: Create and Install Temporary Certificates in WCF for Transport Security During Development

If you really do not want any security on the transport or message, you can check out this link; Introducing WCF ClearUsernameBinding, but I have not tested that solution.

Step 1: Create an Empty Solution.

Start Visual Studio and create a new Blank Solution. I will call this solution “Demo Solution”.

pic1

Step 2: Create a Windows Service to host your WCF Service

Add a new (Visual C#) Windows Service project to your solution. I will call this project “Demo Windows Service”.

pic2

Add references to the following .NET components;

System.Runtime.Serialization
System.Security
System.ServiceModel
System.Web

Now, add a reference to the assembly that implements your Custom Membership Provider, Custom Membership User and Customer Role-Provider. The downloadable code that comes with this HOW TO implements all of this in a seperate project ”Demo Membership”.

Last, add an installer to your Windows Service by selecting the Service1.cs file and clicking on “Add Installer”.

pic3

Step 3: Implement a WCF Service inside the Windows Service

In the Windows Service project, add a class that will can be used for throwing FaultExceptions to your clients. I will call the class “ErrorResponse”. Import the System.Runtime.Serialization namespace and implement the class as such;

 1: [DataContractAttribute]
 2:  public class ErrorResponse
 3: {
 4:  private string message;
 5: [DataMemberAttribute]
 6:  public string Message
 7: {
 8: get { return this.message; }
 9: set { this.message = value; }
 10: }
 11:  public ErrorResponse(string message)
 12: {
 13:  this.message = message;
 14: }
 15: }

In the Windows Service project, add a class that will define the Interface for the service. I will call the class “IHostedService”. Import the System.ServiceModel namespace and implement the interface as such;

 1: [ServiceContract]
 2:  public interface IHostedService
 3: {
 4: [OperationContract]
 5: [FaultContractAttribute(typeof(ErrorResponse))]
 6:  string Hello(string message);
 7: }

Now, inside the same project, add a class the implements the IHostedService interface. I will call it “HostedService”. Import the System, System.Security.Permissions, System.ServiceModel, System.Web.Security namespaces and implement the class as such;

 1:  public class HostedService : IHostedService
 2: {
 3: [PrincipalPermission(SecurityAction.Demand, Role = "RequiredRole")]
 4:  public string Hello(string message)
 5: {
 6:  if (message.Equals(message.ToUpper()))
 7: {
 8:  throw new FaultException<ErrorResponse>(new ErrorResponse("Do not type in capitals!"));
 9: }
 10:  string userName = ServiceSecurityContext.Current.PrimaryIdentity.Name;
 11:  // You can also access the Membership User Object;
 12:  // DemoMembershipUser u = (DemoMembershipUser)Membership.GetUser();
 13:  return String.Format("{0} says: {1}", userName, message);
 14: }
 15: }

 

Remember that the code here references the implementation of the Custom Membership User. In your situation, the references might be different.

Now, change the OnStart() and OnStop() methods of your Windows Service to start (and stop) the WCF Service. Please import the System.ServiceModel namespace in the Service1.cs file.

 1:  public partial class Service1 : ServiceBase
 2: {
 3:  public ServiceHost serviceHost = null;
 4:  
 5:  public Service1()
 6: {
 7: InitializeComponent();
 8: }
 9:  
 10:  protected override void OnStart(string[] args)
 11: {
 12:  if (serviceHost != null)
 13: {
 14: serviceHost.Close();
 15: }
 16: serviceHost = new ServiceHost(typeof(HostedService));
 17: serviceHost.Open();
 18: }
 19:  
 20:  protected override void OnStop()
 21: {
 22:  if (serviceHost != null)
 23: {
 24: serviceHost.Close();
 25: serviceHost = null;
 26: }
 27: }
 28: }

 

Step 4: Configuring your Windows Service.

Next, we need to add a configuration file to the Windows Service to define the proper endpoint, binding and behavior for the WCF Service that is hosted in this Windows Service.

Add a configuration file to the Windows Service by adding a new item of type “Application Configuration File” and name it App.config The file will open up automatically in Visual Studio.

We need to add several different items to the configuration file. Let’s begin by adding the required configuration items for our Custom Membership Provider and Custom Role-Provider.

1. Add a connection string for the Custom Membership Provider to the configuration.

 1: <connectionStrings>
 2: <clear />
 3: <add name="DemoMembershipConnection" connectionString="Driver={SQL Server};Server=(local);Trusted_Connection=No;Database=DemoMembershipDatabase;User Id=DemoMembershipUser;Password=DemoMembershipPassword;" providerName="System.Data.Odbc"/>
 4: </connectionStrings>

 

2. Add a <system.web> section with <membership> and <roleManager> sections. Pay special attention to the “type” attribute. You should set it to the class name of name class that implements the provider, followed by a comma and then the name of the assembly without an extension. So if you have your Provider defined in a class ‘DemoMembershipProvider’ in the namespace ‘My.Demo.Namespace’ that is compiled into the assembly ‘MyMembershipImplementation.dll’ you should set type to: “My.Demo.Namespace.DemoMembershipProvider, MyMembershipImplementation”.Furthermore, if your custom provider class is defined with another class, you should use a plus between the containing class and your provider class. So if, in this example, the ‘DemoMembershipProvider’ class is defined in another class called ‘DemoMembershipStuff’, the type string should read; “My.Demo.Namespace.DemoMembershipStuff+DemoMembershipProvider, MyMembershipImplementation”. If you set this string incorrect, you will see that the services fails to start with an error (in the eventlog) like this;

Service cannot be started. System.TypeLoadException: Could not load type ‘Demo_Membership.MembershipProvider’ from assembly ‘System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.

 1: <system.web>
 2: <membership defaultProvider="DemoMembershipProvider" userIsOnlineTimeWindow="15">
 3: <providers>
 4: <clear />
 5: <add
 6: name="DemoMembershipProvider"
 7: type="Demo_Membership.DemoMembershipProvider, Demo Membership"
 8: connectionStringName="DemoMembershipConnection"
 9: applicationName="DemoWindowsService"
 10: passwordFormat="Clear"
 11: />
 12: </providers>
 13: </membership>
 14: <roleManager enabled="true" defaultProvider="DemoRoleProvider">
 15: <providers>
 16: <clear/>
 17: <add
 18: name="DemoRoleProvider"
 19: connectionStringName="DemoMembershipConnection"
 20: applicationName="DemoWindowsService"
 21: type="Demo_Membership.DemoRoleProvider, Demo Membership" />
 22: </providers>
 23: </roleManager>
 24: </system.web>

 

3. Add a <system.serviceModel> section to the configuration. In this section we define the services, behaviors and bindings for the WCF Service that is hosted in the Windows Service.

Start with the <service> definition:

 1: <services>
 2: <service name="Demo_Windows_Service.HostedService" behaviorConfiguration="HostedServiceBehavior">
 3: <host>
 4: <baseAddresses>
 5: <add baseAddress="http://localhost:8080/HostedService" />
 6: </baseAddresses>
 7: </host>
 8: <endpoint address="" binding="wsHttpBinding" contract ="Demo_Windows_Service.IHostedService" bindingConfiguration="wsHttpEndpointBinding"/>
 9: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
 10: </service>
 11: </services>

 

Add a <behavior>:

Since we are using username/password authentication we need to secure the transport, the message or both. In this demo I will use Message Securtiy. That would mean that certificates have to be installed. But don’t worry; here’s what needs to be done to create and install the certificates for the demo to work (Please see How to: Create and Install Temporary Certificates in WCF for Message Security During Development for detailed instructions):

makecert -n "CN=RootCATest" -r -sv RootCATest.pvk RootCATest.cer

makecert -crl -n "CN=RootCATest" -r -sv RootCATest.pvk RootCATest.crl

Note; I you cannot find the makecert.exe tool: it’s in the Windows SDK. For example; C:\Program Files\Microsoft SDKs\Windows\v6.1\Bin

Import the certificate and the certificate revocation list in the Trusted Root Certification Authorities in you machine account (not your user account). In this HOW TO I will assume that you followed all the directions on the MSDN How To and that you have a new “tempCert” certificate.

 1: <behaviors>
 2: <serviceBehaviors>
 3: <behavior name="HostedServiceBehavior">
 4: <serviceMetadata httpGetEnabled="true" />
 5: <serviceDebug includeExceptionDetailInFaults="true" />
 6: <serviceCredentials>
 7: <serviceCertificate findValue="CN=tempCert" />
 8: <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="DemoMembershipProvider" />
 9: </serviceCredentials>
 10: <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="DemoRoleProvider" />
 11: </behavior>
 12: </serviceBehaviors>
 13: </behaviors>
 

Add a <binding>:

 1: <bindings>
 2: <wsHttpBinding>
 3: <binding name="wsHttpEndpointBinding">
 4: <security>
 5: <message clientCredentialType="UserName" />
 6: </security>
 7: </binding>
 8: </wsHttpBinding>
 9: </bindings>

 

All the hard work is how done. Pleas build the Windows Service.

Step 5: Install and start the Windows Service.

As an administrator, start a command box and navigate to the directory where you build the Windows Service. By default, this is the project directory followed by bin/Debug.

From that directory, use InstallUtil.exe to install the service on your machine. (If you cannot find InstallUtil; it should be located in the directory “C:\Windows\Microsoft.NET\Framework\v2.0.50727”.

The command should look like this:

installutil “Demo Windows Service.exe”.

If all went well, the service should now be available on your system.

pic4

You can now start the service (by means of the management console or command line).

C:\>net start Service1

The Service1 service is starting.

The Service1 service was started successfully.

C:\>

Since the service is now started, it should expose it’s interface to the world. We can open it up at the URL specified in the App.config file’s baseAddress setting for the service. In our example that would be: http://localhost:8080/HostedService

pic5

You can also checkout the WSDL that .Net generated for you at http://localhost:8080/HostedService?wsdl

pic6

The service is up and running. You are done. But we will add a client to the project, so you can see that it actually works.

Step 6: Create a client for the WCF Service.

Add a Console Application project to your solution, name it “Demo Client”.

pic7

Set it to be the StartUp Project for our solution.

Right click the References folder and add a Service Reference.

In the Address: box, enter the URL (In our Demo that would be “http://localhost:8080/HostedService?wsdl”.) of the WSDL for the WCF Service and click on the “Go” button. The “HostedService” service will be found at the URL specified and you can now give a namespace to this service reference. We will use “HostedServiceReference”.

pic8

Click “OK” to create the service reference.

In your Program.cs file, import the namespace you just created (In our demo it’s Demo_Client.HostedServiceReference.) , System.ServiceModel and System.ServiceModel.Security namespaces.

Implement the Program class like this;

 1:  class Program
 2: {
 3:  static void Main(string[] args)
 4: {
 5:  while (true)
 6: {
 7: Console.Write("Username: ");
 8:  string userName = Console.ReadLine();
 9:  if (string.IsNullOrEmpty(userName))
 10: {
 11: Console.WriteLine("Bye.");
 12:  return;
 13: }
 14: Console.Write("Password: ");
 15:  string password = Console.ReadLine();
 16: Console.Write("Message: ");
 17:  string message = Console.ReadLine();
 18: Console.WriteLine(DoRequest(userName, password, message));
 19: }
 20: }
 21:  static string DoRequest(string userName, string password, string message)
 22: {
 23: HostedServiceClient client = new HostedServiceClient();
 24: client.ClientCredentials.UserName.UserName = userName;
 25: client.ClientCredentials.UserName.Password = password;
 26:  string result;
 27:  try
 28: {
 29: result = string.Format("Response: {0}", client.Hello(message));
 30: }
 31:  catch (MessageSecurityException error)
 32: {
 33: result = string.Format("Authentication Error: {0}", error.InnerException.Message);
 34: }
 35:  catch (FaultException<ErrorResponse> error)
 36: {
 37: result = string.Format("ErrorResponse: {0}", error.Detail.Message);
 38: }
 39:  finally
 40: {
 41:  if (client.State == CommunicationState.Opened)
 42: {
 43: client.Close();
 44: }
 45: }
 46:  return result;
 47: }
 48: }

 

The client is now done.

Step 7: Testing it all.

Start your Demo Client, and enter the proper credentials.

In order for the demo to work, you will have to setup a proper database for the Custom Membership Provider. In the HostedService.cs the role that the user has to be in in order for the method to be invoked is “RequiredRole”. If the user is not in that role, the call will fail with a MessageSecurityException.

Step 8: Cleaning up.

After testing it all, please remove the installed service by using InstallUtil. Furthermore, remove the installed temporary certificates. You can also delete the database if it won’t be used anymore.

Sample Code

Download Sample Code Here.