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:
· 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
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades 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), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn