Monday 21 April 2014

WCF Service over HTTP with custom username and password validation in IIS

WCF 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. The best 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.

I create a class and name it CustomValidator.cs. We the implement this code in it:

Note: 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.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IdentityModel.Selectors;
using System.ServiceModel;

namespace Services
{
    public class CustomValidator : UserNamePasswordValidator
    {
        // This method validates users. It allows in two users, test1 and test2
        // with passwords 1tset and 2tset respectively.
        // This code is for illustration purposes only and
        // must not be used in a production environment because it is not secure.
        /// <summary>
        /// userName userId~MachineName
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        public override void Validate( string userName, string password )
        {
            if (null == userName || null == password)
            {
                throw new ArgumentNullException();
            }         

            if (!( userName == "Amit" && password == "Gupta" ))
            {
                // This throws an informative fault to the client.
                throw new FaultException("Unknown Username or Incorrect Password");
                // When you do not want to throw an infomative fault to the client,
                // throw the following exception.
                // throw new SecurityTokenException("Unknown Username or Incorrect Password");
            }
        }
    }
}



Creating the service

Add file .svc file in your project for example is used BusinessPartner.svc
The WCF Service just contains a function Select_Schedule():


   [ServiceContract]
    public class BusinessPartner : IBusinessPartner
    {

        [OperationContract]
        public DateTime Select_Schedule(  )
        {
            try
            {
                DateTime dt=DateTime.Now;
                return dt;
            }
            catch (Exception ex)
            {
                throw new FaultException(Utility.HandleException(ex).ToString());
            }
        }
    }



Configuring the Web Service

Modify the web.config and add following lines in it.

a) Behavior setting
<behaviors>
      <serviceBehaviors>       

        <behavior name="Services.EZJemsBehaviorValidator">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <serviceCertificate findValue="EzJemshttp"  storeLocation="LocalMachine" storeName="My" x509FindType="" />
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Services.CustomValidator, Services" />           
          </serviceCredentials>
        </behavior>


        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>



b) Binding setting
<bindings>     
      <wsHttpBinding>
        <binding name="wsHttpBinding_EZJEMS" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00"
                 sendTimeout="00:10:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                 maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" messageEncoding="Text"
                 textEncoding="utf-8" useDefaultWebProxy="true">
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>



<service behaviorConfiguration="Services.EZJemsBehaviorValidator" name="Services.BusinessPartner">
        <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpBinding_EZJEMS" contract="Services.BusinessPartner" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost/" />
          </baseAddresses>
        </host>
      </service>




Creating the site in IIS 7

Open IIS Manager. Right click Sites and Add Website. Name it as Services, set Application pool to DeafaultAppPool, and select the physical path.


Now browse the site & verify the service, it should be up.

The final step is to create a client to consume the service


Add the app.config and add following lines in it.

<wsHttpBinding>
        <binding name="wsHttpBinding_EZJEMS" closeTimeout="00:10:00"
          openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"
          bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
          maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" messageEncoding="Text"
          textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647"
            maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
          <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
          <security mode="Message">
            <transport clientCredentialType="Windows" proxyCredentialType="None"
              realm="" />
            <message clientCredentialType="UserName" negotiateServiceCredential="true"
              algorithmSuite="Default" />
          </security>
        </binding>
</wsHttpBinding>


<client>
      <endpoint address="http://115.111.112.43/EZJEMSServicesOld/BusinessPartner.svc"
       binding="wsHttpBinding" bindingConfiguration="wsHttpBinding_EZJEMS"
       contract="BusinessPartner.BusinessPartner" name="WSHttpBinding_BusinessPartner">
      </endpoint>
    </client>

The client application is the desktop application Used service method in your application using below code:

BusinessPartner.BusinessPartnerClient wsBusinessPartner = new BusinessPartner.BusinessPartnerClient();

wsBusinessPartner.ClientCredentials.UserName.UserName = "Amit";
wsBusinessPartner.ClientCredentials.UserName.Password = "Gupta";

DateTime dt = wsBusinessPartner.Select_Schedule(intIdPartner, id_Entity);



About Author:
Amit Gupta is technology lead in Systems Plus Pvt. Ltd and keen to resolve challenges using his technical skills. He actively contributes to technology and can be contacted at: amit.gupta@spluspl.com

1 comment: