Authorization and Authentication using WCF Security - Silverlight

Posted by: Mahesh Sabnis , on 10/18/2010, in Category Silverlight 2, 3, 4 and 5
Views: 55972
Abstract: In this article, we will explore WCF Security for authorization and authentication.
In my previous articles Silverlight 4.0 - Calling Secured WCF 4.0 Service hosted with SSL and Self-Signed Certificate, we saw how to consume WCF SSL enabled service in Silverlight 4.0 client and in the article Silverlight 4.0 - Secure Communication to WCF service using Custom User Name and Password Validator, we saw how to authenticate a user using by using custom user name and password. As an extension to these articles, we will now  explore how to authenticate a Silverlight user against WCF service to perform business operations like Read All and Insert etc.
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.       
In this article, we will design a WCF service which is hosted on IIS with SSL and Self signed certificate. The complete process of configuring and comsuming SSL and self signed certificates is already explained here Silverlight 4.0 - Calling Secured WCF 4.0 Service hosted with SSL and Self-Signed Certificate
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:
image_5
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:
image_1
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’.
image_2 
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:
image_3
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:
image_4
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.

Absolutely Awesome Book on C# and .NET

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 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 eBook 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 the 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!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

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 x on Friday, November 26, 2010 3:43 PM
cool
Comment posted by Mahesh Sabnis on Tuesday, November 30, 2010 5:37 AM
Dear Mr. X
  Thanks a lot.
Mahesh Sabnis
Comment posted by Adnan on Friday, December 3, 2010 10:43 AM
Hi Mahesh, This is a great article!  
Couple of questions: Are you using Windows Authentication in IIS?  IS SSL still enabled?  Could you please share your IIS settings with us? Thanks in advance!
Comment posted by Mahesh Sabnis on Saturday, December 18, 2010 12:36 AM
Hi Adnan,
Yes I am using Windows Authentication here. The IIS settings with SSL are still enabled. You can vist http://www.dotnetcurry.com/ShowArticle.aspx?ID=589 for my IIS settings for SSL.

Thanks
Mahesh Sabnis
Comment posted by Chandresh Makwana on Thursday, June 16, 2011 5:20 PM
Hi Mahesh,
The articles that you have written here are fantastic and very useful. With respect to the same, I am facing a problem in my hosting / development domain, as explained below:
My Silverlight 4 mapping application is configured as out of browser with elevated trusts applied. This is required, as it consumes the local hardware resources using Automation features. Further, the same Silverlight application also consumes WCF services from the same domain or from different domain, but all hosted in HTTPS channels. It also consumes map layers using the layer service URLs, which are again hosted using HTTPS channel in the same or different domain.
But invoking the application in out of browser doesn't allow me consume HTTPS service at all, throwing the exception - NotFound. Whereas, my services are hosted with self-signed certificates generated in IIS 7.5, and with valid private key and expiration date. Also, I am able to browse the WCF service HTTPS URL in IE, without giving me any error. Some interesting things I have noted about this:
The application runs absolutely well when a Fiddler 2.0 session is open with Fiddler tools options set to intercept HTTPS traffic (in Fiddler UI, Tools -> Fiddler Options -> HTTPS tab -> check Capture HTTPS Connects and Intercept HTTPS traffic). In this case, the Fiddler generates a temporary DO_NOT_TRUST_FIDDLER certificate, and performs the HTTPS communication using this certificate, as a valid HTTPS client.

When I consume the map service layers published with valid third party server certificate (GTE CyberTrust), then it works absolutely well. But it allows only that layer to be rendered. The other layers as well as the WCF services are consumable, throwing the same NotFound exception.

Please let me know if you have any hint on this.
Comment posted by Chandresh Makwana on Friday, June 17, 2011 12:14 PM
Hi Mahesh,
The articles that you have written here are fantastic and very useful. With respect to the same, I am facing a problem in my hosting / development domain, as explained below:
My Silverlight 4 mapping application is configured as out of browser with elevated trusts applied. This is required, as it consumes the local hardware resources using Automation features. Further, the same Silverlight application also consumes WCF services from the same domain or from different domain, but all hosted in HTTPS channels. It also consumes map layers using the layer service URLs, which are again hosted using HTTPS channel in the same or different domain.
But invoking the application in out of browser doesn't allow me consume HTTPS service at all, throwing the exception - NotFound. Whereas, my services are hosted with self-signed certificates generated in IIS 7.5, and with valid private key and expiration date. Also, I am able to browse the WCF service HTTPS URL in IE, without giving me any error. Some interesting things I have noted about this:
The application runs absolutely well when a Fiddler 2.0 session is open with Fiddler tools options set to intercept HTTPS traffic (in Fiddler UI, Tools -> Fiddler Options -> HTTPS tab -> check Capture HTTPS Connects and Intercept HTTPS traffic). In this case, the Fiddler generates a temporary DO_NOT_TRUST_FIDDLER certificate, and performs the HTTPS communication using this certificate, as a valid HTTPS client.

When I consume the map service layers published with valid third party server certificate (GTE CyberTrust), then it works absolutely well. But it allows only that layer to be rendered. The other layers as well as the WCF services are consumable, throwing the same NotFound exception.

Please let me know if you have any hint on this.

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

C# .NET BOOK

C# Book for Building Concepts and Interviews

Tags

JQUERY COOKBOOK

jQuery CookBook