DotNetCurry Logo

Implementing Transport Message Security with WCF 4.0 and VS 2010 RC

Posted by: Mahesh Sabnis , on 3/30/2010, in Category Windows Communication Foundation (WCF)
Views: 66475
Abstract: This article is Part 1 of a two part series where I will demonstrate how to create a certificate, create a WCF 4.0 service that uses this certificate and finally host a WCF service on IIS 7.5 with SSL. In this article, we will see how to create a certificate and create a WCF service that uses this certificate.
Implementing Transport Message Security with WCF 4.0 and VS 2010 RC
 
I hope most of you have downloaded VS 2010 RC by now and have started exploring its new features. I have received feedback in the past where developers have faced challenges in implementing security for WCF services in their applications. One of the nice features of WCF Security is that we can configure it using a custom user name – password validation mechanism.  In this two part series, I will explain how to create a certificate, create a WCF service configured to use this certificate and host the WCF service on IIS 7.5 with SSL. In this article, I will show you how to create a certificate and create a WCF service which uses this certificate. In this next part of this article, I will demonstrate how to host this WCF Service on IIS 7.5 with SSL.
I am developing this solution on Windows 7 Enterprise edition.
Creating X509 Certificate
 
Step 1: Since we need to use SSL for transport security, a certificate must be created and configured with the WCF service. To create a certificate, follow the steps shown below:
i)             Open command prompt of VS2010 and write the following command for creating a certificate:
makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyCustomUsrPwdCert –sky exchange –pe
 
(Note: If you want to know more about this command you can visit this link)
ii)            You can view this certificate created in the certificate store. For this purpose, you need to use the Microsoft Management Console (MMC).
iii)           This certificate should be available in the ‘Personal’ store of the ‘LocalMachine’ account so that it can be configured for the WCF service. (Note: You need to use either ‘CertMgr’ command line tool or MMC for import and export certificates.)
Creating WCF Service using SSL
 
Step 2: Open VS2010 and create a blank solution, name it as ‘WCF_SecurityPractice’.
Step 3: To this solution, add a WCF service application project and name it as ‘WCF_CustomUNamePwdeSecureService’. Rename ‘IService1.cs’ to ‘IService.cs’, rename ‘Service1.cs’ to ‘Service.cs’.
Step 4: Add the following code in ‘Iservice.cs’
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
 
namespace WCF_CustomUNamePwdeSecureService
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        List<clsEmployee> GetAllEmployees();
    }
 
    [DataContract]
    public class clsEmployee
    {
        [DataMember]
        public int EmpNo { get; set; }
        [DataMember]
        public string EmpName { get; set; }
    }
}
 
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.Serialization
Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.Text
 
Namespace WCF_CustomUNamePwdeSecureService
      <ServiceContract> _
      Public Interface IService
            <OperationContract> _
            Function GetAllEmployees() As List(Of clsEmployee)
      End Interface
 
      <DataContract> _
      Public Class clsEmployee
            Private privateEmpNo As Integer
            <DataMember> _
            Public Property EmpNo() As Integer
                  Get
                        Return privateEmpNo
                  End Get
                  Set(ByVal value As Integer)
                        privateEmpNo = value
                  End Set
            End Property
            Private privateEmpName As String
            <DataMember> _
            Public Property EmpName() As String
                  Get
                        Return privateEmpName
                  End Get
                  Set(ByVal value As String)
                        privateEmpName = value
                  End Set
            End Property
      End Class
End Namespace
 
Step 5: Write the following code in ‘Service.cs’. This code implements ‘IService’ interface in ‘Service’ class.
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Security;
 
namespace WCF_CustomUNamePwdeSecureService
{
    public class Service : IService
    {
        public List<clsEmployee> GetAllEmployees()
        {
            List<clsEmployee> lstEmp = null;
            if (OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.IsAuthenticated == false)
            {
                throw new SecurityException();
            }
            else
            {
                lstEmp = new List<clsEmployee>()
                {
                    new clsEmployee() {EmpNo=101,EmpName="Mahesh 1"},
                    new clsEmployee() {EmpNo=102,EmpName="Mahesh 2"},
                    new clsEmployee() {EmpNo=103,EmpName="Mahesh 3"},
                    new clsEmployee() {EmpNo=104,EmpName="Mahesh 4"},
                    new clsEmployee() {EmpNo=105,EmpName="Mahesh 5"},
                    new clsEmployee() {EmpNo=106,EmpName="Mahesh 6"},
                    new clsEmployee() {EmpNo=107,EmpName="Mahesh 7"},
                    new clsEmployee() {EmpNo=108,EmpName="Mahesh 8"},
                    new clsEmployee() {EmpNo=109,EmpName="Mahesh 9"},
                    new clsEmployee() {EmpNo=1010,EmpName="Mahesh 10"}
                };
            }
            return lstEmp;
        }
    }
}
 
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.Serialization
Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.Text
Imports System.Security
 
Namespace WCF_CustomUNamePwdeSecureService
      Public Class Service
            Implements IService
            Public Function GetAllEmployees() As List(Of clsEmployee)
                  Dim lstEmp As List(Of clsEmployee) = Nothing
                  If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.IsAuthenticated = False Then
                        Throw New SecurityException()
                  Else
                        lstEmp = New List(Of clsEmployee) (New clsEmployee() {New clsEmployee() With {.EmpNo=101, .EmpName="Mahesh 1"}, New clsEmployee() With {.EmpNo=102, .EmpName="Mahesh 2"}, New clsEmployee() With {.EmpNo=103, .EmpName="Mahesh 3"}, New clsEmployee() With {.EmpNo=104, .EmpName="Mahesh 4"}, New clsEmployee() With {.EmpNo=105, .EmpName="Mahesh 5"}, New clsEmployee() With {.EmpNo=106, .EmpName="Mahesh 6"}, New clsEmployee() With {.EmpNo=107, .EmpName="Mahesh 7"}, New clsEmployee() With {.EmpNo=108, .EmpName="Mahesh 8"}, New clsEmployee() With {.EmpNo=109, .EmpName="Mahesh 9"}, New clsEmployee() With {.EmpNo=1010, .EmpName="Mahesh 10"}})
                  End If
                  Return lstEmp
            End Function
      End Class
End Namespace
 
Step 6: Now since the service is going to authenticate caller using ‘UserName’ and ‘Password’, we need to write a custom validator. To do this, follow the steps shown below;
·         Right click on the WCF service project and add reference of ‘System.IdentityModel’ namespace.
·         If you have a SQL Server Database. create a table of name ‘Users’ with following Schema:
Users_Table
·         To the project, add a class, name it as ‘AuthenticationHelper’ and write the following code:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel;
using System.IdentityModel.Selectors;
using System.Data.SqlClient;
using System.Data;
 
namespace WCF_CustomUNamePwdeSecureService
{
    public class AuthenticationHelper : UserNamePasswordValidator
    {
        SqlConnection Conn;
        SqlCommand Cmd;
        SqlDataReader Reader;
 
        public override void Validate(string userName, string password)
        {
            if (userName == null || password == null)
            {
                throw new Exception("User Name or Password cannot be null");
            }
 
            if (!this.CheckIfUserNameExist(userName))
            {
                throw new Exception("Sorry!This User is Not Present");
            }
 
            if (!this.AuthenticateUser(userName, password))
            {
                throw new Exception("Invalid User Name or Password");
            }
 
 
        }
 
        #region Service Method
        //Following Method check wheather UserName is present in table or not
        private bool CheckIfUserNameExist(string userName)
        {
            bool Exists = false;
            Conn = new SqlConnection("Data Source=.\\dbserver;Initial Catalog=Company;Integrated Security=SSPI");
            Cmd = new SqlCommand();
            Conn.Open();
            Cmd.CommandText = "Select UserName from Users where UserName=@UserName";
            Cmd.Connection = Conn;
            Cmd.Parameters.AddWithValue("@UserName", userName);
            Reader = Cmd.ExecuteReader();
            DataTable DtUser = new DataTable();
            DtUser.Load(Reader);
 
            int Count = DtUser.Rows.Count; //Check the Count of Rows
            if (Count != 0) //If row count is <> 0 then user exists
                Exists = true;
            Conn.Close();
            return Exists;
        }
 
        private bool AuthenticateUser(string userName, string password)
        {
            bool Valid = false;
 
            Conn = new SqlConnection("Data Source=.\\dbserver;Initial Catalog=Company;Integrated Security=SSPI");
            Cmd = new SqlCommand();
            Conn.Open();
            Cmd.CommandText = "Select Password from Users where UserName=@UserName";
            Cmd.Connection = Conn;
            Cmd.Parameters.AddWithValue("@UserName", userName.Trim());
            Reader = Cmd.ExecuteReader();
            Reader.Read();
            if (Reader["Password"].ToString() == password.Trim())
                Valid = true;
 
            Conn.Close();
            return Valid;
 
        }
        #endregion
 
    }
}
 
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.IdentityModel
Imports System.IdentityModel.Selectors
Imports System.Data.SqlClient
Imports System.Data
 
Namespace WCF_CustomUNamePwdeSecureService
      Public Class AuthenticationHelper
            Inherits UserNamePasswordValidator
            Private Conn As SqlConnection
            Private Cmd As SqlCommand
            Private Reader As SqlDataReader
 
            Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
                  If userName Is Nothing OrElse password Is Nothing Then
                        Throw New Exception("User Name or Password cannot be null")
                  End If
 
                  If (Not Me.CheckIfUserNameExist(userName)) Then
                        Throw New Exception("Sorry!This User is Not Present")
                  End If
 
                  If (Not Me.AuthenticateUser(userName, password)) Then
                        Throw New Exception("Invalid User Name or Password")
                  End If
 
 
            End Sub
 
            #Region "Service Method"
            'Following Method check wheather UserName is present in table or not
            Private Function CheckIfUserNameExist(ByVal userName As String) As Boolean
                  Dim Exists As Boolean = False
                  Conn = New SqlConnection("Data Source=.\dbserver;Initial Catalog=Company;Integrated Security=SSPI")
                  Cmd = New SqlCommand()
                  Conn.Open()
                  Cmd.CommandText = "Select UserName from Users where UserName=@UserName"
                  Cmd.Connection = Conn
                  Cmd.Parameters.AddWithValue("@UserName", userName)
                  Reader = Cmd.ExecuteReader()
                  Dim DtUser As New DataTable()
                  DtUser.Load(Reader)
 
                  Dim Count As Integer = DtUser.Rows.Count 'Check the Count of Rows
                  If Count <> 0 Then 'If row count is <> 0 then user exists
                        Exists = True
                  End If
                  Conn.Close()
                  Return Exists
            End Function
 
            Private Function AuthenticateUser(ByVal userName As String, ByVal password As String) As Boolean
                  Dim Valid As Boolean = False
 
                  Conn = New SqlConnection("Data Source=.\dbserver;Initial Catalog=Company;Integrated Security=SSPI")
                  Cmd = New SqlCommand()
                  Conn.Open()
                  Cmd.CommandText = "Select Password from Users where UserName=@UserName"
                  Cmd.Connection = Conn
                  Cmd.Parameters.AddWithValue("@UserName", userName.Trim())
                  Reader = Cmd.ExecuteReader()
                  Reader.Read()
                  If Reader("Password").ToString() = password.Trim() Then
                        Valid = True
                  End If
 
                  Conn.Close()
                  Return Valid
 
            End Function
            #End Region
 
      End Class
End Namespace
 
The above class is inherited from ‘UserNamePasswordValidator’ class. This class provides ‘Validate’ method which needs to be overridden by using custom logic for validating user name and password. In this article, I am connecting to my SQL server 2008 instance to verify user name and password, passed by my client application.
Step 7: Now open the Web.config file and write the following configuration:
<?xmlversion="1.0"?>
<configuration>
 <system.web>
    <compilationdebug="true"targetFramework="4.0" />
 </system.web>
 <system.serviceModel>
    <services>
      <servicename="WCF_CustomUNamePwdeSecureService.Service"behaviorConfiguration="MyBehavior">
        <endpointaddress=""
                  binding="basicHttpBinding"
                  contract="WCF_CustomUNamePwdeSecureService.IService"
                   bindingConfiguration="MyBind">
          <identity>
            <dnsvalue="localhost"/>
          </identity>
        </endpoint>
        <endpointaddress="mex"binding="mexHttpsBinding"contract="IMetadataExchange"/>
      </service>
 
    </services>
    <bindings>
      <basicHttpBinding>
        <bindingname="MyBind">
          <securitymode="TransportWithMessageCredential">
            <messageclientCredentialType="UserName"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behaviorname="MyBehavior">
          <serviceCredentials>
            <serviceCertificatestoreName="My"storeLocation="LocalMachine"x509FindType="FindBySubjectName"findValue="MyCustomUsrPwdCert"/>
            <userNameAuthenticationuserNamePasswordValidationMode="Custom"
                                    customUserNamePasswordValidatorType="WCF_CustomUNamePwdeSecureService.AuthenticationHelper,WCF_CustomUNamePwdeSecureService"/>
          </serviceCredentials>
          <serviceMetadata/>
          <serviceDebugincludeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
 </system.serviceModel>
 <system.webServer>
    <modulesrunAllManagedModulesForAllRequests="true"/>
 </system.webServer>
 
</configuration>
 
The above configuration is explained here:
·         BasicHttpBinding is used so that the WCF service can be used by all types of clients including Silverlight 3.0.
·         TransportWithMessageCredential is used to provide transport level security over Http using Security Socket Layer (SSL).
·         In the Binding configuration, client credential types is defined to ‘UserName’. This demands that the consumer of the service must pass a valid user credential for establishing successful communication with WCF service.
·         In the behavior configuration, the certificate which we have created is specified for encryption.
·         ‘userNameAuthentication’ here defines what component is used to verify user name and password passed by the client application.
So now your WCF service is ready to be deployed.

Conclusion: In this article, I showed you how to create a certificate and create a WCF service which uses this certificate. In the next article, I will demonstrate how to host this WCF Service on IIS 7.5 with SSL

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Mahesh Sabnis is a DotNetCurry author and Microsoft MVP having over 17 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). Follow him on twitter @maheshdotnet


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Anup Hosur on Tuesday, March 30, 2010 4:11 AM
Hi Mahesh,
    you have not shown, how to host in IIS 7.5 and on ssl in this demo. Please click on this link for ssl
http://msdn.microsoft.com/en-us/library/ms733791.aspx

Thanks,
Anup
Comment posted by Shawn on Tuesday, March 30, 2010 9:18 PM
The author clearly mentions "In this article, I will show you how to create a certificate and create a WCF service which uses this certificate. In this next part of this article, I will demonstrate how to host this WCF Service on IIS 7.5 with SSL."
Comment posted by Mahesh on Wednesday, March 31, 2010 4:37 AM
Hi Anup,

  Wait till the next article, I am going to explain all steps of hosting on IIS 7.5 and SSL.

Comment posted by Anup Hosur on Friday, April 2, 2010 7:00 AM
Sorry, for not reading the article carefully.
Comment posted by Andrew Griffiths on Monday, November 22, 2010 8:31 AM
I've tried Hosting WCF 4.0 Service on IIS 7.5 with SSL and i can't resolve this error "The type 'WCF_SecurityPractice.Service', provided as the Service attribute value in the ServiceHost directive,could not be found."
Comment posted by Mahesh Sabnis on Tuesday, November 30, 2010 5:43 AM
Hi Andrew Griffiths,
  Make sure that you have made necessary changes in Web.Config file for <service name="<Service Name here>">. Also in Service.svc file make sure that the value of the "Service" attribute of "@ServiceHost" is matching with the name of the service in Web.Config file.
Thanks
Mahesh Sabnis
Comment posted by Mike on Friday, July 29, 2011 5:55 AM
Great article!  Thank you for writing it.  I'm curious about one part.  Does this code expose the service to sql injection?
Cmd.CommandText = "Select UserName from Users where UserName=@UserName";
            Cmd.Connection = Conn;
            Cmd.Parameters.AddWithValue("@UserName", userName);
Comment posted by Ashot on Monday, November 14, 2011 7:36 AM
I have an error on step 1:
makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyCustomUsrPwdCert –sky exchange –pe
Error: Too many parameters ...

Does anyone has same error?
Comment posted by Ashot on Monday, November 14, 2011 7:45 AM
I have an error on step 1:
makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyCustomUsrPwdCert –sky exchange –pe
Error: Too many parameters ...

Does anyone has same error?
Comment posted by Nishant on Tuesday, July 24, 2012 4:18 AM
hi Mahesh is it possible to run the same on IIS 5.1 and VS 2008....
Comment posted by Sandeep Kumar on Wednesday, January 2, 2013 6:27 AM
Hi Mahesh
         When i view this wcf service in browser i got an exception "Could not find a base address that matches scheme https for the endpoint with binding BasicHttpBinding. Registered base address schemes are [http]. ".
How i can resolve this?
Comment posted by Sandeep Kumar on Wednesday, January 2, 2013 6:50 AM
Hi Mahesh
         When i view this wcf service in browser i got an exception "Could not find a base address that matches scheme https for the endpoint with binding BasicHttpBinding. Registered base address schemes are [http]. ".
How i can resolve this?
Comment posted by sdf on Friday, August 22, 2014 6:33 AM
<s>