To perform these operations, I have used the Windows Server 2008 R2 with Active Directory configurations and added two users in it as shown below:
· Domain Name: Mithilla.
· User 1: Leena.
· User 2: Tejas.
In .NET framework we have been provided with ‘System.Security’ namespace using which ‘PrincipalPermission’ can be set on various operations exposed by WCF services. This object provides ‘Name’ and ‘Role’ properties using which operations can be configured against user name of its role, to execute a specific operation in the WCF service.
Creating a WCF service with Authorization attributes
Step 1: Open VS2010 and create a blank solution and name it as ‘WCF_Authorization_Authentication’. In this solution, add a WCF service project and name it as ‘WCF_Authorization_Service’. Rename ‘IService1.cs’ to ‘IService’, rename ‘Service1.svc’ to ‘Service.svc’.
Step 2: Right click ‘Service.svc’ and select ‘View Markup’ and change the ‘Service’ attribute of @ServiceHost as below:
<%@ ServiceHost Language="C#" Debug="true" Service="WCF_Authorization_Service.Service" CodeBehind="Service.svc.cs" %>
Step 3: Open ‘IService.cs’ and write the following code for ServiceContract and OperationContract etc.
C#
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace WCF_Authorization_Service
{
[ServiceContract]
public interface IService
{
[OperationContract]
List<Employee> GetAllEmployees();
[OperationContract]
[FaultContract(typeof(CustomFaultMessage))]
void CreateEmployee(Employee objEmp);
}
[DataContract]
public class Employee
{
[DataMember]
public int EmpNo { get; set; }
[DataMember]
public string EmpName { get; set; }
[DataMember]
public int Salary { get; set; }
[DataMember]
public int DeptNo { get; set; }
}
[DataContract]
public class CustomFaultMessage
{
private string _FaultReason;
[DataMember]
public string FaultReason
{
get { return _FaultReason; }
set { _FaultReason = value; }
}
}
}
Step 4: Open Service.svc.cs and implement the ‘IService’ interface in it as below:
C#
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Security.Permissions;
using System.Data.SqlClient;
using System.Security;
namespace WCF_Authorization_Service
{
public class Service : IService
{
List<Employee> lstEmp;
SqlConnection Conn;
SqlCommand Cmd;
public Service()
{
Conn = new SqlConnection("Data Source=.;Initial Catalog=Company;Integrated Security=SSPI");
}
[PrincipalPermission(SecurityAction.Demand, Name = @"mithilla\Leena")]
[PrincipalPermission(SecurityAction.Demand,Name=@"mithilla\tejas")]
public List<Employee> GetAllEmployees()
{
Conn.Open();
Cmd = new SqlCommand("Select * from Employee",Conn);
SqlDataReader Reader = Cmd.ExecuteReader();
List<Employee> lstEmp = new List<Employee>();
while (Reader.Read())
{
lstEmp.Add
(
new Employee()
{
EmpNo=Convert.ToInt32(Reader["EmpNo"]),
EmpName= Reader["EmpName"].ToString(),
Salary = Convert.ToInt32(Reader["Salary"]),
DeptNo = Convert.ToInt32(Reader["DeptNo"])
}
);
}
Cmd.Dispose();
Conn.Close();
return lstEmp;
}
[PrincipalPermission(SecurityAction.Demand, Name = @"mithilla\Leena")]
public void CreateEmployee(Employee objEmp)
{
try
{
Conn.Open();
Cmd = new SqlCommand();
Cmd.Connection = Conn;
Cmd.CommandText = "Insert into Employee Values(@EmpNo,@EmpName,@Salary,@DeptNo)";
Cmd.Parameters.AddWithValue("@EmpNo", objEmp.EmpNo);
Cmd.Parameters.AddWithValue("@EmpName", objEmp.EmpName);
Cmd.Parameters.AddWithValue("@Salary", objEmp.Salary);
Cmd.Parameters.AddWithValue("@DeptNo", objEmp.DeptNo);
Cmd.ExecuteNonQuery();
Conn.Close();
}
catch (SecurityException ex)
{
CustomFaultMessage customFault = new CustomFaultMessage();
customFault.FaultReason = "Error: " + ex.Message.ToString();
new FaultException<CustomFaultMessage>(customFault);
}
catch (Exception ex)
{
CustomFaultMessage customFault = new CustomFaultMessage();
customFault.FaultReason = "Error: " + ex.Message.ToString();
throw new FaultException<CustomFaultMessage>(customFault);
}
}
}
}
In the above code, PrincipalPermission object defined on the methods specifies which user(s) are able to call OperationContract. In this the ‘GetAllEmployees()’ method is accessible to ‘Leena’ and ‘Tejas’ both, where as ‘CreateEmployee()’ method is accessible to only the user ‘Leena’. This implies that if the client application makes call using user ‘Tejas’ then the ‘CreateEmployee()’ method call will raise a security exception.
Step 5: Open the web.config file and make the following changes:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="WCF_Authorization_Service.Service"
behaviorConfiguration="ServBehave">
<endpoint
address=""
binding="basicHttpBinding"
bindingConfiguration="basicBind"
contract="WCF_Authorization_Service.IService"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="basicBind">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="ServBehave">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
The above configuration uses BasicHttpBinding with security mode as ‘TransportWithMessageCredentials’ which imples that the consumer of this WCF service must send credential (UserName and Password) while making the request. ClientCredentialType=Windows denotes that the credentials send by the sender will be verified against Windows.
Step 6: In the project properties, select ‘Web’ table and set the Web server for Deployement as shown below:
Click on ‘Create Virtual Directory’ to create a Web Site on IIS 7.x, which is configured for HTTPS with SSL and Self signed certificate.
Step 7: Build the project and browse the Service.svc file.
Creating Silverlight Client Application
In this task, we will create a Silverlight 4 client application. This will consume the WCF service and make a call to the service using client credentials. The failure/unauthenticated call output will be shown using “Debug” mode by attaching w3wp.exe worker process to the WCF service, targeted to .NET 4.0 framework.
Step 1: In the same solution created above, add a new Silverlight 4.0 application and name it as ‘SILV4_WCF_Authorization’.
Step 2: Open MainPage.xaml and write the following XAML code:
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="469*" />
<ColumnDefinition Width="425*" />
</Grid.ColumnDefinitions>
<Grid Height="287" HorizontalAlignment="Left" Margin="31,24,0,0" Name="grid1" VerticalAlignment="Top" Width="389">
<sdk:DataGrid AutoGenerateColumns="True" Height="211" HorizontalAlignment="Left" Margin="22,62,0,0" Name="dgEmp" VerticalAlignment="Top" Width="340" />
<TextBlock Height="41" HorizontalAlignment="Left" Margin="42,13,0,0" Name="textBlock5" Text="Employee Details" VerticalAlignment="Top" Width="280" TextAlignment="Center" FontWeight="ExtraBold" FontSize="20" />
</Grid>
<Grid Grid.Column="1" Height="298" HorizontalAlignment="Left" Margin="14,13,0,0" Name="grid2" VerticalAlignment="Top" Width="388">
<Grid.RowDefinitions>
<RowDefinition Height="47*" />
<RowDefinition Height="44*" />
<RowDefinition Height="43*" />
<RowDefinition Height="47*" />
<RowDefinition Height="152*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="195*" />
<ColumnDefinition Width="193*" />
</Grid.ColumnDefinitions>
<TextBlock Height="36" HorizontalAlignment="Left" Margin="6,6,0,0" Name="textBlock1" Text="EmpNo" VerticalAlignment="Top" Width="144" />
<TextBlock Grid.Row="1" Height="25" HorizontalAlignment="Left" Margin="8,9,0,0" Name="textBlock2" Text="EmpName" VerticalAlignment="Top" Width="149" />
<TextBlock Grid.Row="2" Height="34" HorizontalAlignment="Left" Margin="6,5,0,0" Name="textBlock3" Text="Salary" VerticalAlignment="Top" Width="147" />
<TextBlock Grid.Row="3" Height="34" HorizontalAlignment="Left" Margin="6,6,0,0" Name="textBlock4" Text="DeptNo" VerticalAlignment="Top" Width="144" />
<TextBox Grid.Column="1" Height="26" HorizontalAlignment="Left" Margin="10,6,0,0" Name="txteno" VerticalAlignment="Top" Width="142" />
<TextBox Grid.Column="1" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,7,0,0" Name="txtename" VerticalAlignment="Top" Width="140" />
<TextBox Grid.Column="1" Grid.Row="2" Height="32" HorizontalAlignment="Left" Margin="12,4,0,0" Name="txtsal" VerticalAlignment="Top" Width="140" />
<TextBox Grid.Column="1" Grid.Row="3" Height="34" HorizontalAlignment="Left" Margin="14,6,0,0" Name="txtdno" VerticalAlignment="Top" Width="138" />
<Button Content="Crerate New Employee" Grid.Column="1" Grid.Row="4" Height="37" HorizontalAlignment="Left" Margin="24,33,0,0" Name="btnCreateNewEmp" VerticalAlignment="Top" Width="140" Click="btnCreateNewEmp_Click" />
</Grid>
<Button Content="Get All Employees" Height="34" HorizontalAlignment="Left" Margin="96,316,0,0" Name="btnGetAllEmployees" VerticalAlignment="Top" Width="156" Click="btnGetAllEmployees_Click" />
</Grid>
The design of the window will be as shown below:
Step 3: Add the WCF service reference in the SL application. Make sure that the endpoint address uses ‘Host Machine name’ rather than ‘localhost’ because the host is IIS and a self-signed certificate is used, which does not understand ‘localhost’.
Step 4: Open MainPage.xaml.cs and do the following:
Declare the WCF service proxy object at class level as below:
C#
MyRef.ServiceClient Proxy;
Write the following code in loaded event:
C#
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
bool registerResult = WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
Proxy = new MyRef.ServiceClient();
Proxy.ClientCredentials.UserName.UserName = @"mithilla\tejas";
Proxy.ClientCredentials.UserName.Password = "P@ssw0rd_";
}
The above code is very important as the code clearly shows that the client application is passing the username as ‘Leena’. This user is authenticated to perform ‘GetAllEmployees’ and ‘CreateEmployee’ operations on the WCF service.
Write the following code on ‘Get All Employees’ button click event
C#
private void btnGetAllEmployees_Click(object sender, RoutedEventArgs e)
{
try
{
Proxy.GetAllEmployeesCompleted += new EventHandler<MyRef.GetAllEmployeesCompletedEventArgs>(Proxy_GetAllEmployeesCompleted);
Proxy.GetAllEmployeesAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void Proxy_GetAllEmployeesCompleted(object sender, MyRef.GetAllEmployeesCompletedEventArgs e)
{
try
{
if (e.Result != null)
{
dgEmp.ItemsSource = e.Result;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Write the following code for ‘Create New Employee’ button click
C#
private void btnCreateNewEmp_Click(object sender, RoutedEventArgs e)
{
try
{
MyRef.Employee obj = new MyRef.Employee();
obj.EmpNo = Convert.ToInt32(txteno.Text);
obj.EmpName = txtename.Text;
obj.Salary = Convert.ToInt32(txtsal.Text);
obj.DeptNo = Convert.ToInt32(txtdno.Text);
Proxy.CreateEmployeeCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(Proxy_CreateEmployeeCompleted);
Proxy.CreateEmployeeAsync(obj);
}
catch (FaultException<MyRef.CustomFaultMessage> ex)
{
MessageBox.Show("Reported Error :" + ex.Detail.FaultReason);
}
catch (FaultException ex)
{
MessageBox.Show("Reported Error :" + ex.Message);
}
catch (CommunicationException ex)
{
MessageBox.Show("Reported Error :" + ex.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void Proxy_CreateEmployeeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
MessageBox.Show("Insert Completed");
}
else
{
if (e.Error is FaultException<MyRef.CustomFaultMessage>)
{
FaultException<MyRef.CustomFaultMessage> serviceFault = e.Error as FaultException<MyRef.CustomFaultMessage>;
MessageBox.Show(serviceFault.Detail.FaultReason.ToString());
}
}
}
The fault exception is used to catch the exception.
Step 5: Run the SL application and click on ‘Get All Employees’ button and the DataGrid will display all the records. Enter values in textboxes for creating new employee and click on ‘Create New Employee’ button. The ‘Insert Completed message will be displayed’.
Step 6: Now change the login user in loaded event from Leena to Tejas shown below:
Proxy.ClientCredentials.UserName.UserName = @"mithilla\tejas";
Proxy.ClientCredentials.UserName.Password = "Pass123";
Put a break point on the ‘btnCreateNewEmp_Click’ also open ‘Service.Svc.cs’ and put a break point on ‘CreateEmployee’ operation.
Step 7: Run the application, click on ‘Get All Employees’ and you will get all records displayed in DataGrid because the user ‘Tejas’ is authenticated to perform this operations. Now enter data in Textboxes for creating a new employee and click on ‘Create New Employee’ button. You will enter the debug mode as shown below:
Open Service.svc.cs and Click on ‘Debug’ menu and attached process ‘W3WP.Exe’ targeted to .NET 4.0 as below:
Now complete debugging in MainPage.xaml.cs and the control will reach to the create employee method. However since the user ‘Tejas’ is not authorized to perform the operation, the following result will be displayed:
The PrincipalPermission attribute clearly specifies that the method is accessible only to user Leena, so no other user can access it.
If you want to control access on group of users then club all such users in a specific group e.g. Manager, Admin etc and then use the PrincipalPermission attribute as shown below:
[PrincipalPermission(SecurityAction.Demand, Role=”Admin”)]
Conclusion:
When developing LOB applications using SL and WCF, for authorization and authentication, the WCF security features proves very useful.
The entire source code of this article can be downloaded over here
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