In order to architect a system that consists of a few different components spread over multiple servers, there are a few concepts that have to be considered during the analysis phase. Security is a critical piece involved in the architecture, and very often may cause change of plans accordingly. The example below represents the following architecture:
· A Windows 2003 Server (either physical or virtual) with IIS 6 running and a WCF service application attached to the IIS. The WCF application references DLLs from other already developed .NET projects that on their turn access various SQL 2005 Databases on multiple SQL Servers (either physical or virtual). The DLLs can either live in one repository somewhere on the IIS server drives, or can be added to the actual WCF Application as External DLLs and then referenced in the application. The latter is adopted in the example below.
· Finally we have a client application that accesses the WCF service on the IIS server. This application can be anything from Windows Forms, to WPF, to ASP.NET, to Silverlight. In the example below, WPF is used.
Let’s look at the following situation: We want the users to freely use their WPF application without having to login providing a username and password every time they start the app. That means that when a user passes a request to the WCF running on a separate server, they will access this server with their domain user name in active directory. From there, let’s assume that the SQL database on the SQL server in a remote location allows users to login using integrated windows security (which is the most secure way and also allows keeping track of who is doing what on the server at anytime). That also will require the same domain name credentials in active directory so the user is able to access the database for their request.
According to global security standards, the user running a WPF (or any other) application on their workstation, which accesses WCF Service on a server, and then attempts to access a SQL database on a remote server, which is also set to use windows security is not permitted. This type of situation is called double-hoping where the user accesses a server with their domain credentials, but then attempt to access a second server with the same credentials. At this point of time, the user’s credentials will be lost, and the database will be attempted to be accessed with the IIS server’s credentials instead which will typically fail. This is implemented exactly for the purpose to avoid double-hopping.
There are different ways to avoid this situation. One example is setting up the SQL database with a generic username and password. Another is including the IIS server’s credentials to the database permitted users. However using the domain user name from active directory and Windows authentication through the different platforms allows for a better security architecture since no passwords are transferred from one location to the next and since it is easier to monitor users’ activity at all times.
That is why one way to overcome the double-hopping problem is by implementing Kerberos authentication, which is a global security protocol developed at MIT (Massachusetts Institute of Technology) sometime in the 90s and is used widely. This document has examples on how to correctly configure your WCF and WPF applications as well as how to modify some of the IIS settings in order to have a working security model.
Note: In order to create the model, the IIS server has to already be set up with Kerberos authentication. The default server setting for Windows 2003 is NTLM authentication. This part is not covered in the document. Also, the security model will work for users that are already in Active Directory, but not for external users. Third party tools will be necessary to expand this functionality to external logins.
Creating the WCF Service application
Step one of the process is to create a WCF Service Application. Note that the .NET Framework version used in the model is 3.5.
To do so, we need to select the WCF Service Application type in visual studio under the Web projects tab.
From there on, we will have to develop the WCF application like we normally would. We need to create our Service Contract and Data Contract classes in the Public Interface with a default name IService1. The following is the representation of the IService1 code.
<ServiceContract()> _
Public Interface IService1
<OperationContract()> _
Function GetData(ByVal data As MyDataContract) As String
End Interface
<DataContract()> _
Public Class MyDataContract
Private _ClaimObject As Claim_Services.Claim
Private _clmObj As Claim_Services.Claim_DataAccess
Private _currentError As Error
<DataMember()> _
Public Property clmObj() As Claim_DataAccess
Get
Return _clmObj
End Get
Set(ByVal value As Claim_DataAccess)
_clmObj = value
End Set
End Property
<DataMember()> _
Public Property currentError() As Error
Get
Return _currentError
End Get
Set(ByVal value As Error)
_currentError = value
End Set
End Property
<DataMember()> _
Public Property ClaimObject() As Claim
Get
Return _ClaimObject
End Get
Set(ByVal value As Claim)
_ClaimObject = value
End Set
End Property
End Class
Note that the WCF Service Application already has a reference to a DLL (Claim_Services.dll) from an old .NET project that contains a method in one of its classes (the Claim class as the ClaimObject data member above suggests), which will call a SQL database in order to retrieve a record. Our WCF application service is in the position of a middle tier between a client application and other projects’ DLLs that are accessing the database for data manipulation. Its role is to just transport data and requests back and forth like a regular web service.
Once the IService1 is completed, we can move on and implement the service in a regular class that is under the Service1.svc object. This is where most of the security settings for the model need to be created.
In order to be allowed to call the WCF application which will be installed on a Web server by attaching it to IIS, and then call a second server (our SQL Database call in this example), we will have to implement impersonation and delegation along with Kerberos authentication for the purpose. Otherwise, the attempt will result in a double-hop which is not permitted by the Security Standards as explained above.
The following shows the implementation of the GetData Operation Contract from the code above.
<OperationBehavior(Impersonation:=ImpersonationOption.Required)> _
Public Function GetData(ByVal data As MyDataContract) As String Implements IService1.GetData
Try
Dim str As String
Dim securityContext As System.ServiceModel.ServiceSecurityContext = System.ServiceModel.OperationContext.Current.ServiceSecurityContext
Dim ID as Integer = 100
If securityContext IsNot Nothing AndAlso securityContext.WindowsIdentity IsNot Nothing Then
Using securityContext.WindowsIdentity.Impersonate
data.clmObj = New Claim_DataAccess
data.currentError = New Error
str = " getting data- " & data.clmObj.GetOneClaim(ID,data.currentError)
str = str & ": " & securityContext.WindowsIdentity.Name
Return str
End Using
End If
Catch ex As Exception
Return ex.Message
End Try
End Function
Note that the only things that the code does, is instantiate the objects that we need to pass to the GetOneClaim method, and format the string that is returned including the WindowsIdentity.Name object which will show us if we have completed the impersonation process correctly. We will have to see our own domain name coming back from the IIS server if the impersonation is correct once we consume the WCF service with our client application.
In order to start impersonation, we have to decorate the function with the (Impersonation:=ImpersonationOption.Required) statement. The only other thing left is to specify WindowsIdentity.Impersonate in the Security Context while we are making the call to the external DLL and therefore the SQL database. Once this part is completed, there are changes that need to be made in the web.config file of the project. The web.config deals with the type of binding, which is the most critical piece of the model. Everything that we need for the impersonation process is part of the <System.ServiceModel> tag of the configuration. This is an example of what needs to be found there:
<system.serviceModel><diagnostics>
<messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<services>
<service behaviorConfiguration="WcfServiceApplication.Service1Behavior" name="WcfServiceApplication.Service1" >
<endpoint binding="basicHttpBinding" bindingConfiguration="basicBinding" address="" contract="WcfServiceApplication.IService1">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceApplication.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<windowsAuthentication includeWindowsGroups="true" allowAnonymousLogons="false"/>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="UseWindowsGroups" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="basicBinding" maxReceivedMessageSize="2000000" >
<readerQuotas maxArrayLength="2000000" maxStringContentLength="2000000" />
<security mode ="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
There are a few different types of binding that are allowed in WCF. The two that allow for Windows Integrated Security are wsHTTPBinding and basicHTTPBinding. The first one comes by default. However for this example, basicHTTPBinding was used. This one is supposedly less secure, but faster. Note that the type of binding is first specified in the <endpoint binding> tag. Then we need to have a detailed node on binding within the <bindings> tag. This is where we set our security mode to TransportCredentialsOnly and our client Credential type to Windows. In the <behaviors tag>, make sure to set allowAnonymousLogons to False.
Also make sure to locate the <authentication mode="Windows" /> and add the following tag after it:
<identity impersonate="true" />
Deploying WCF in IIS and configuring server properties
At this point, make sure that the Windows server is running .NET Framework 2.0 or above. After setting up the WCF application, the next step will be deploying the WCF application on the IIS server used for the model. This is done simply by copying the folder containing the WCF project to the IIS server. To attach the WCF application to IIS, we have to go through the regular Web Sharing process.
· Find the project folder and go the second root level where the solution (.SLN) file is.
· Right-click on the project folder there and choose Properties.
· In the Properties dialog, select Web Sharing, and then in the Share on: dropdown, select the default choice (usually Default website).
· We can alias the service name or leave it as it is.
· Check Read and Directory browsing under Access permissions and then select Execute (includes scripts) under the Application permissions and click OK.
At this point the WCF service should be attached to IIS and running (assuming IIS is running already). To open the IIS manager, go to Control Panel, Administrative Tools and Internet Information Services Manager. Our service should be running under Web Sites / Default Web Site/ Our service name from above.
Now it is time to make sure that the service settings are configured correct.
· Right-click on the service virtual folder and choose Properties.
· In the Properties window, under the Virtual Directory tab, click Configuration.
· Scroll down in the list of Application extensions and make sure that the .SVC extension is available. If not, click on the Add button.
o To add a new .SVC extension, we need to browse to a particular DLL. This is at: c:\windows\microsoft.net\framework\2.0.50727\aspnet_isapi.dll
o Type .SVC in the extension block and click OK.
· Back in the Properties window, click on the ASP.NET tab. Make sure that version 2.0.50727 or higher is selected for ASP.NET version
· Back on the Properties window, click on the Directory Security tab. Click on Edit under Authentication and access control. Make sure that Enable anonymous access is unchecked, and that Integrated Windows authentication is checked
The last very important step that needs to be performed on the IIS server is to assure that the server is trusted for delegation. This is the setting that will allow the server to pass user’s credentials to remote servers after the WCF impersonation process is completed. This is done from the server’s properties in Active Directory. Under the Delegation tab, make sure that “Trust this computer for delegation to any service (Kerberos only)” is selected.
In order to test if our service is running properly, we open an Internet browser on the server and type in http://localhost followed by the name of our WCF service under the Default Web Site in IIS, followed by the file name ending with .SVC in the service directory.
Creating the WPF client application
The last step of the process is to create the WPF client tester application that will consume the WCF service. In order to do so, after the application itself is created in a regular way, we want to add a Service reference to it which will point to the WCF service. To do, type in the hyperlink in the provided space in the Service Reference dialog box and after the service was found successfully click OK.
By adding the service reference, the app.config for the application will be already configured as supposed to including the type of binding that was earlier specified on the WCF side. This information should be all available in the <client> tag.
In order to configure the WPF completely however, there is a statement that needs to be added in code. When trying to use the service reference to access the WCF service, our code should look like the following:
Using service As ServiceReference1.Service1Client = New ServiceReference1.Service1Client
service.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation
End Using
The TokenImpersonationLevel.Delegation, allows the credentials to be delegated from the IIS server to others. When the application is built, the app.config file should be updated with the following line produced from the code that we just added in the <endpointBehaviors> tag:
<windows allowedImpersonationLevel="Delegation"/>
At this point we should be able to run the WPF application which will consume the WCF service in IIS on a server, which will process certain business logic, impersonate the caller, delegate the credentials further on through Kerberos authentication, and return information to the WPF client eventually.