Implementing a UserNameForCertificate Security Using WCF

Overview

In UserNameForCertificate security, the service and client are secured using message-level security. The service is authenticated with an X.509 certificate. The client authenticates using a user name and password (UserNameSecurityToken). You will require adding a reference to System.ServiceModel and System.IdentityModel to your Client/Service application.

Creating a WCF Service

I assume that you have installed the .NET 3.0 framework and Visual Studio 2005 extensions for WCF. To create a WCF service, go to File->New->Project, expand Visual C#, and select Net Framework 3.0; select WCF Service Library, and say OK. The project so created cannot be directly hosted in IIS; in the example provided here, the service is hosted in IIS. Here is how you can host the service in IIS.

  1. Make an App_Code folder in the project.
  2. Move all *.cs files to the App_Code folder.
  3. Add a file ServiceName.svc to your project and write the following line in it:
    < %@ servicehost debug="true" language="c#"
                     service="WCFServiceLibrary.Service" %>
    

    where WCFServiceLibrary is the Namespace and Service is your service class.

  4. Add a web.config file.
  5. Make sure that the Build output of the project compilation goes to bin.
  6. Make a virtual directory in IIS with the same name as project and point to the folder containing *.svc file.

Creating the Service

Step 1: Define a Contract

To create the Service, you need to define a Contract. A contract is an interface that defines the signature of service methods. Here is how you do it.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace WCFServiceLibrary
{
   [ServiceContract]
   public interface IService
   {
      [OperationContract]
         string Hello();
      [OperationContract]
         string HelloDataContract(Data dataContractValue);
   }
}

The ServiceContract attribute indicates that the interface defines a Service contract for WCF; the attribute can be directly applied to a class. The OperationContract attribute is applied to methods and indicates that the method will be visible to the service clients.

Step 2: Define the Service class or implement IService

//ServiceBehavior tells service to include exception details as
//service faults and send to client
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Service : IService
{
   public string Hello()
   {
      //here is how you can retrieve userNameToken
      //If you want to get only user name use
      //OperationContext.Current.ServiceSecurityContext.
      //PrimaryIdentity.Name
      UserNameSecurityToken token = null;
      string message = "Hello ";
      foreach (SupportingTokenSpecification
               supportingTokenSpecification in
               OperationContext.Current.SupportingTokens)
      {
         if (supportingTokenSpecification.SecurityToken is
             UserNameSecurityToken)
         {
            token = (UserNameSecurityToken)
               supportingTokenSpecification.SecurityToken;

            break;
         }
      }
      if (token != null)
      {
         message = message + token.UserName;
      }

      return message;
   }
}

Step 3: Create the Service configuration file

Add a web.config file to the Service project and add the following XML. You can see in the XML below that there are three important sections:

  1. Endpoint: The endpoint is where you define the address of the service and associate bindings and behaviors with the service.
  2. Bindings: Bindings are objects used to specify the communication details that are required to connect to the endpoint of a Windows Communication Foundation (WCF) service.

    See the binding named UserNameForCertificateBinding; it has a security element that is used to specify the type of security supported by the endpoint.

    Security has following attributes:

    1. authenticationMode = “UserNameForCertificate”. This indicates that you are using UserNameForCertificate.
    2. messageProtectionOrder=”SignBeforeEncrypt”. This indicates that the message is first signed and then encrypted before it is transmitted on the network.
  3. Behaviors: As the name indicates, behaviors define the behavior of the service at runtime.

    See the behavior named ServiceBehaviorUserName. It uses the userNameAuthentication element to specify that you are using a custom authentication and a customUserNamePasswordValidatorType attribute defines the name of the class that will handle the authentication. See the Implementing Custom User name validator section for the implementation of custom authentication. if you do not require custom authentication, just remove the userNameAuthentication element from the behavior.

  4. <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
       <system.serviceModel>
          <services>
             <service name="WCFServiceLibrary.Service"
                behaviorConfiguration="ServiceBehaviorUserName">
             <!-- This endpoint is exposed at the base address
                  provided by the host-->
                <endpoint address=""
                          binding="customBinding"
                          contract="WCFServiceLibrary.IService"
                          bindingConfiguration=
                             "UserNameForCertificateBinding" />
    
                <endpoint contract="IMetadataExchange"
                          binding="mexHttpBinding" address="mex" />
             </service>
          </services>
    
          <bindings>
             <customBinding>
                <binding name="UserNameForCertificateBinding">
                   <textMessageEncoding messageVersion="Soap12"
                      writeEncoding="utf-8">
                      <readerQuotas maxDepth="32"
                         maxStringContentLength="999999999"
                         maxArrayLength="999999999"
                         maxBytesPerRead="4096"
                         maxNameTableCharCount="999999999" />
                   </textMessageEncoding>
                   <security authenticationMode=
                      "UserNameForCertificate"
                      messageProtectionOrder="SignBeforeEncrypt"
                      requireDerivedKeys="true"
                      includeTimestamp=
                         "true" messageSecurityVersion=
                         "WSSecurity11WSTrustFebruary2005WSSecure
                          ConversationFebruary2005WSSecurity
                          Policy11BasicSecurityProfile10">
                   </security>
                   <httpTransport authenticationScheme="Anonymous"/>
                </binding>
             </customBinding>
          </bindings>
    
          <behaviors>
             <serviceBehaviors>
                <behavior name="ServiceBehaviors">
                   <serviceMetadata httpGetEnabled="true" />
                </behavior>
                <behavior name="ServiceBehaviorUserName" >
                   <serviceMetadata httpGetEnabled="true" />
                   <serviceCredentials>
                     <userNameAuthentication
                        userNamePasswordValidationMode="Custom"
                        customUserNamePasswordValidatorType=
                        "WCFServiceLibrary.CustomUserNameValidator,
                         WCFServiceLibrary" />
                     <serviceCertificate
                        storeLocation="LocalMachine"
                        storeName="My"
                        findValue="CN=ServiceCertficate"
                        x509FindType=
                           "FindBySubjectDistinguishedName"/>
                    </serviceCredentials>
                </behavior>
             </serviceBehaviors>
          </behaviors>
       </system.serviceModel>
    
       <system.web>
          <!--
             Set compilation debug="true" to insert debugging
             symbols into the compiled page. Because this affects
             performance, set this value to true only during
             development.
          -->
             <compilation debug="true"/>
       </system.web>
    </configuration>
    
  5. To run this service, you must create a certificate named ServiceCertficate. You can create this certificate by executing the following command at the Visual Studio command prompt:

    makecert -sk  ServiceCertficate
             -n   "CN=ServiceCertficate"
             -ss  My
             -sr  LocalMachine
             -sp  "Microsoft Enhanced Cryptographic Provider v1.0"
             -sky exchange -r C:ServiceCertficate.cer
    

    The command above creates a certificate in the personal store of LocalMachine and exports the public key to C:ServiceCertficate.cer.

    Import C:ServiceCertficate.cer in personal store of
           "Current/loggedin" windows user
    
  6. Give read access on the following folder to ASP.NET user/Current user

    C:Documents and SettingsAll UsersApplication Data
       MicrosoftCryptoRSAMachineKeys
    
  7. Now, compile the application and access the URL http://localhost/ProjectName/ServiceName.svc. You should get an error-free page that gives you information to generate a service proxy.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read