Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Build a WCF Authentication Self-host App with SSL

3.67/5 (2 votes)
25 Aug 2013CPOL2 min read 20.6K  
Build a username authentication WCF self-host application with SSL

Introduction

This is a wizard to lead you to complete the WCF service & client base on wsHttpBinding without certificate.

Background

I take the order to investigate the WCF authentication with username. So I found out about basicHttpBinding & wsHttpBinding. In this tip, I will show you how to use wsHttpBinding with SSL to communicate with authentication WCF service application.

And you may know how to build a WCF application. So, this page is not pointing to how to build a WCF application, but points to how to build a custom username validator & how to build the configuration file.

Using the Code

There are two parts of the application, of which one is the service part, and the other is the client part.

In the server part, you could build a class named CustomUserNameValidator and inherit UserNamePasswordValidator. And implement the abstract method "Validate". And you can throw some exception when the username and password are invalidated.

The Service Work

  • You should complete the validator code.
  • You should make a certificate for the transport encryption, and you can follow this article.
  • You should make a certificate for the SSL cert, and you can follow this article.
  • Bind you service application to the SSL.
  • Finish your service configuration.

The Validator Code

C#
public class CustomUserNameValidator : UserNamePasswordValidator
    {
        private const string USERNAME_ELEMENT_NAME = "userName";

        private const string PASSWORD_ELEMENT_NAME = "password";

        private const string FAULT_EXCEPTION_MESSAGE = "UserName or Password is incorrect!";

        public override void Validate(string userName, string password)
        {
            Guarder.Guard.ArgumentNotNull(userName)
                .ArgumentNotNull(password);
            var validateUserName = ConfigurationManager.AppSettings[USERNAME_ELEMENT_NAME];
            var validatePassword = ConfigurationManager.AppSettings[PASSWORD_ELEMENT_NAME];
            var validateCondition = userName.Equals(validateUserName) && password.Equals(validatePassword);
            if (!validateCondition)
            {
                throw new FaultException(FAULT_EXCEPTION_MESSAGE);
            }
        }
    } 

Make the Certificate of your Transport Encryption

makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=ServerCert -sky exchange –pe 

Bind your Application to the SSL Code

netsh http add sslcert ipport=0.0.0.0:12221 certhash=32e636c3cefb16726b88b082b22b51194d77f865 
appid={ba77e2bd-7499-44ee-bfb0-b45188f94e7f} clientcertnegotiation=enable 

In the code, certhash is the thumbprint of the certificate, you can get it in the mms application, which is the native tool of Windows. And the appid, you can get it from the assembly information of the project.

And the Service Configuration

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <appSettings>
    <add key="username" value="your username"/>
    <add key="password" value="your password"/>
  </appSettings>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="securityBehavior">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <!--makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=ServerCert -sky exchange –pe-->
            <serviceCertificate
              findValue="ServerCert"
              x509FindType="FindBySubjectName"
              storeLocation="CurrentUser"
              storeName="My"/>
            <userNameAuthentication
              userNamePasswordValidationMode="Custom"
              customUserNamePasswordValidatorType=
              "TimeSynchronizeHttpsServer.CustomUserNameValidator,TimeSynchronizeHttpsServer"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <wsHttpBinding>
        <binding name="securityMessageBinding">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="Basic"/>
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="TimeSynchronizeHttpsServer.TimeSynchronizeService" 
      behaviorConfiguration="securityBehavior">
        <!--to use https should bind ssl to url follow 
        http://www.run-corp.com/how-to-configure-self-hosted-wcf-server-endpoint/ 
        and use a difference cert with your service certificate.-->
        <!--When you bind certificate to ssl url there is a parameter named appid, 
        it is the server guid of assembly info-->
        <endpoint address="https://127.0.0.1:12221/TimeSynchronize"
                  binding="wsHttpBinding"  
                  bindingConfiguration="securityMessageBinding"
                  contract="TimeSynchronizeHttpsServer.ITimeSynchronizeService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" 
        contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8733/Design_Time_Addresses/
            TimeSynchonizeHttpsServer/TimeSynchronizeService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>   

The Client Work

  • Finish the client configuration
  • Complete the client custom validator
  • Complete the client code

Finish the Client Configuration

The client configuration is like below:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <appSettings>
    <add key="username" value="your username"/>
    <add key="password" value="your password"/>
  </appSettings>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="securityMixedBinding">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="Basic"/>
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="https://127.0.0.1:12221/TimeSynchronize"
                binding="wsHttpBinding" bindingConfiguration="securityMixedBinding"
                contract="ITimeSynchronizeService" 
                name="DefaultBinding_ITimeSynchronizeService_ITimeSynchronizeService" />
    </client>
  </system.serviceModel>
</configuration> 

The Client Custom Validator

C#
class CertificateValidator
    {
        public static void SetCertificatePolicy ( )
        {
            ServicePointManager.ServerCertificateValidationCallback
                       += RemoteCertificateValidate;
        }
        private static bool RemoteCertificateValidate 
        ( object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error )
        {
            System.Console.WriteLine ( "Warning, trust any certificate" );
            return true;
        }
    } 

The validator's usage is to ensure the remote certificate validate. Because the certificate is published by ourselves, so we should cheat the server to get passed. And we should call the method "SetCertificatePolicy" before we call the contract.

The Client Application Code

C#
class Program
    {
        private const string USERNAME = "userName";
        private const string PASSWORD = "password";
        static void Main ( string[] args )
        {
            var proxy = new TimeSynchronizeServiceClient ( );
            var userName = ConfigurationManager.AppSettings[USERNAME];
            var password = ConfigurationManager.AppSettings[PASSWORD];
            proxy.ClientCredentials.UserName.UserName = userName;
            proxy.ClientCredentials.UserName.Password = password;
            CertificateValidator.SetCertificatePolicy ( );
            var time = proxy.GetTime ( );
            var builder = new StringBuilder ( );
            builder.Append ( "Server time is:" ).Append ( " " ).Append ( time );
            var message = builder.ToString ( );
            Console.WriteLine ( message );
            Console.ReadKey ( );
        }
    } 

Result

I searched a lot of documents on the internet, and modified some demos by myself. And this document can be passed when I run the application. I hope this page is useful for you.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)