Authentication and Authorization in WCF Services - Part 1
Robert Green
MCW Technologies
Download
Articles in this series
- Introduction to Windows Communication Foundation
- Hosting Windows Communication Foundation Services
- Self Hosting Windows Communication Foundation Services
- Creating and Maintaining Service and Data Contracts
- Fault Handling in WCF Services
- Sessions, Instancing and Concurrency in WCF Services
- Transactions in WCF Services
- Message Patterns in WCF Services
- Authentication and Authorization in WCF Services - Part 1
- Authentication and Authorization in WCF Services - Part 2
Introduction
When client applications and WCF services communicate, they do so by passing XML messages. Securing these messages is an important part of building, hosting and calling WCF services. In this two-part tutorial, you will ask questions related to security. For example:
- Can any client call the service or do you want to control who can call the service?
- Can any client call any method of the service or do you want to control what clients can call what methods?
- Can any client execute all of the code in a method or do you want to control what clients can execute what code?
The answers to these questions determine your use of authentication and authorization. Authentication enables you to identify clients that call the service. Authorization enables you to determine what operations authenticated clients can access. You will typically base authorization on roles.
To authorize, you first need to authenticate. To do that, you must be able to identify clients. Clients can identify themselves by providing evidence such as a Windows account, a user name / password or a certificate. Clients must also know that they are calling the service they intend to call. Services can identify themselves by providing a certificate.
In this two-part tutorial, you will see how to authenticate clients and control their access in the following three scenarios:
- Internal Self-Hosted Services. You are self-hosting a WCF service on the corporate network. You want anyone who can log onto the network to be able to access the service. You want only certain users to be able to call particular methods.
- Internal Web-Hosted Services. You are hosting a WCF service using Internet Information Services on the corporate intranet. Both employees and guests have access to the wireless network. You want only employees to be able to call the service. You want only certain users to be able to call particular methods.
- Public Web-Hosted Services. You are hosting a WCF service publicly on the Internet. You want to limit access to the service to users with a valid user name and password. You want only certain users to be able to call particular methods.
Before you explore authentication and authorization, you need a basic understanding of WCF security.
Review Basic WCF Security Concepts
When you write a WCF service, you write it using the .NET Framework and either C# of Visual Basic. To the extent that a WCF service is a .NET application, you can use familiar security techniques, including code access and role-based security. However, securing the code that runs on the computer hosting the service is only part of the story.
Remember that the client and service communicate by exchanging XML messages. You may need to secure these messages as well. Protecting messages as they are transferred from client to server and back is known as transfer security. WCF provides two mechanisms for transfer security: transport security and message security.
Transport Security and Message Security
If you use transport security, security occurs at the transport level. The packets sent “on the wire” include the caller’s credentials and the message. Both of which are encrypted using whatever mechanism the transport protocol uses. For example, if you use TCP, you will likely use Transport Layer Security (TLS) and if you use HTTPS, you will likely use Secure Sockets Layer (SSL).
If you use message security, the caller’s credentials are included in the message and the message is encrypted using the WS-Security specification.
It is generally faster to encrypt and decrypt messages that use transport security and you can benefit from hardware acceleration to improve performance.
A downside to transport security is that messages are encrypted only from point to point. Suppose a client sends a message to a service. The client encrypts the message and the service decrypts it. If the service then forwards the message to another service, the service forwarding the message will not automatically encrypt it. This is not an issue with message security because the service will encrypt the message before passing it on to another service.
A downside to message security is that it requires both clients and services to support the WS-Security specification. Transport security does not have this requirement and is therefore more interoperable.
Configure Security
WCF supports the following six security modes:
- None. Messages are not secured.
- Transport. Messages are secured using transport security. You will use this in the sample applications that demonstrate the first two scenarios (internal self-hosted and Web-hosted services).
- Message. Messages are secured using message security. You will use this in the sample application that demonstrates the first scenario (internal self-hosted service).
- TransportWithMessageCredential. Message protection and authorization occur at the transport level and credentials are passed with the message. You will use this in the sample application that demonstrates the third scenario (public Web-hosted service).
- TransportCredentialOnly. Credentials are passed at the transport level but the message is not encrypted. This option is available only if you are using the BasicHttpBinding binding.
- Both. Messages are secured using both transport level and message level security. This is supported only if you are using Microsoft Message Queue Server.
Each binding has a default set of security settings. By default, the following bindings use message security: WSHttpBinding, WS2007HttpBinding, WSDualHttpBinding, WSFederationBinding and WS2007FederationBinding.
By default, the following bindings use transport security: NetTcpBinding, NetNamedPipesBinding, NetMsmqBinding, NePeerBinding and MsmqIntegrationBinding.
By default, the BasicHttpBinding binding uses None as its security mode. In other words, message sent using that binding are not secure. This enables interoperability with ASMX Web services.
You can modify the settings by modifying properties of the binding and by specifying service behaviors. You will see examples of how to do this in this tutorial. For more detailed information, see the Programming WCF Security (https://msdn.microsoft.com/en-us/library/ms731925.aspx) and Bindings and Security (https://msdn.microsoft.com/en-us/library/ms731172.aspx) Help topics.
Authenticate Clients and Services
Authentication enables you to identify clients and services. They identify themselves by passing credentials. WCF supports the following credential types when you are using transport level security:
- Windows. The client uses a Windows token representing the logged in user’s Windows identity. The service uses the credentials of the process identity or an SSL certificate. You will use this in the sample application that demonstrates the first scenario (internal self-hosted service).
- Basic. The client passes a user name and password to the service. Typically, the user will enter the user name and password in a login dialog box. The service uses a SSL certificate. This option is available only with HTTP protocols. You will use this in the sample application that demonstrates the second scenario (internal Web-hosted service).
- Certificate. The client uses an X.509 certificate and the service uses either that certificate or an SSL certificate.
- NTLM. The service validates the client using a challenge/response scheme against Windows accounts. The service uses a SSL certificate. This option is available only with HTTP protocols.
- None. The service does not validate the client.
WCF supports the following credential types when you are using message level security:
- Windows. The client uses a Windows token representing the logged in user’s Windows identity. The service uses the credentials of the process identity or an SSL certificate. You will use this in the sample application that demonstrates the first scenario (internal self-hosted service).
- UserName. The client passes a user name and password to the service. Typically, the user will enter the user name and password in a login dialog box. The service can validate the user name and password using a Windows account or the ASP.NET membership provider. You will use this in the sample application that demonstrates the third scenario (public Web-hosted service).
- Certificate. The client uses an X.509 certificate and the service uses either that certificate or an SSL certificate.
- IssueToken. The client and service use the Secure Token Service, which issues tokens the client and service trust. Windows CardSpace uses the Secure Token Service.
- None. The service does not validate the client.
Authorize Clients
Authorization enables you to determine what operations authenticated clients can access. WCF supports three basic approaches to authorization:
- Role-based. Access to a service and to operations of the service is based on the user’s role. All of the samples in this tutorial demonstrate the use of role-based authorization.
- Identity based. Access is based on claims made within the user’s credentials. This is an extension to role-based authorization and provides a more fine grained approach. This approach will typically be used with issue token authentication.
- Resource based. Resources, such as WCF services, are secured using Windows Access Control Lists (ACLs).
You have three options when deciding how to determine a user’s role:
- Windows groups. You can use the built-in Windows groups such as Administrators or Power Users or create your own Windows groups. You will use this in the sample applications that demonstrate the first two scenarios (internal self-hosted and Web-hosted services).
- Custom roles. You can create roles that are specific to your application, such as Manager, Employee, Administrator, etc.
- ASP.NET role management. You can use the ASP.NET role provider and use roles you have defined for a Web site. You will use this in the sample application that demonstrates the third scenario (public Web-hosted service).
Control Access to and Usage of Internal Self-Hosted Services
The first example in this tutorial involves an internal self-hosted service. In this scenario, you are self-hosting a WCF service on the corporate network. You want anyone who can log onto the network to be able to access the service. However, you want only certain users to be able to call particular methods.
To explore authentication and authorization in this scenario, start Visual Studio 2008 and select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Navigate to the InternalSelfHostedServiceDemo folder. Select InternalSelfHostedServiceDemo.sln and click OK to open the project. The sample application includes three projects. The SecureServiceLibrary project contains a simple WCF service. The ConsoleHost project uses a console application to host the service.
The WCF service has two operations. The SayHello method returns a string that includes the name of the logged in user on the client computer. The ReportSales method returns $10,000, representing today’s sales.
// C#
public string SayHello()
{
return string.Format("Hello {0}",
System.Threading.Thread.CurrentPrincipal.Identity.Name);
}
public decimal ReportSales()
{
return 10000M;
}
' Visual Basic
Public Function SayHello() As String Implements ISecureService.SayHello
Return String.Format("Hello {0}", _
Threading.Thread.CurrentPrincipal.Identity.Name)
End Function
Public Function ReportSales() As Decimal _
Implements ISecureService.ReportSales
Return 10000D
End Function
Press F5 to run the application. You may see a Windows Security Alert (see Figure 1). Click Unblock to enable the service host to listen for client requests over TCP.
Figure 1. Click Unblock to enable the service host to listen for requests.
You should see the following displayed in the console window:
The service is running and is listening on:
net.tcp://localhost:9000/SecureService (NetTcpBinding)
https://localhost:8090/SecureService (WSHttpBinding)
net.tcp://localhost:9001/mex (MetadataExchangeTcpBinding)
Press any key to stop the service.
The app.config file in the ConsoleHost project defines three endpoints for the service. Clients can communicate with the service using either the NetTcpBinding or WSHttpBinding bindings. When you add a service reference, Visual Studio uses the third endpoint to download metadata from the service.
In the form, select NetTcpBinding and then click Say hello. The application should display a message similar to the one shown in Figure 2, but with your logged in user name.
Figure 2. The service returns to the client a greeting with your name.
Click OK to dismiss the message. Select WSHttpBinding and click Say hello. You should see the same greeting. Click OK to dismiss the message. Click Report sales. You should see a message informing you that today’s sales are $10,000. Click OK to dismiss the message. Close the form and then press any key in the console window to stop the service.
When you clicked the Say hello button, you called the WCF service, which returned a string containing your user name. To understand how this happened, open the app.config file in the WindowsClient project. You will see the following:
<bindings>
<netTcpBinding>
<binding name="SecureService_Tcp"
…
<security mode="Transport">
<transport clientCredentialType="Windows"
protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
<wsHttpBinding>
<binding name="SecureService_WsHttp"
…
<security mode="Message">
<transport clientCredentialType="Windows"
proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows"
negotiateServiceCredential="true"
algorithmSuite="Default"
establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
By default, the NetTcpBinding binding uses transport security.
<security mode="Transport">
By default, the WSHttpBinding binding uses transport security.
<security mode="Message">
By default, both bindings use Windows as the client credential type.
<transport clientCredentialType="Windows"
<message clientCredentialType="Windows"
When the client code creates a new instance of the service proxy class, it passes your Windows credentials to the service. The SayHello method code uses Threading.Thread.CurrentPrincipal.Identity.Name to identify the user by name and build the greeting string.
// C#
public string SayHello()
{
return string.Format("Hello {0}",
System.Threading.Thread.CurrentPrincipal.Identity.Name);
}
' Visual Basic
Public Function SayHello() As String Implements ISecureService.SayHello
Return String.Format("Hello {0}", _
Threading.Thread.CurrentPrincipal.Identity.Name)
End Function
The CurrentPrincipal property of the Thread class returns an instance of the IPrincipal interface. It represents the security context of the user on whose behalf the code is running. This context includes the user’s identity and roles to which he or she belongs. The Identify property of IPrincipal returns the identity. This property is an instance of the IIdentity interface.
Now that you know how to tell who is calling the service, you can add code to control what code they can execute. In the Solution Explorer, double-click the SecureService file. Add the following to the top of the code file:
// C#
using System.Security.Permissions;
' Visual Basic
Imports System.Security.Permissions
Make the following change to the ReportSales method:
// C#
[PrincipalPermission(SecurityAction.Demand,
Role="BUILTIN\\BackupOperators")]
public decimal ReportSales()
' Visual Basic
<PrincipalPermission(SecurityAction.Demand, _
Role:="BUILTIN\\BackupOperators")> _
Public Function ReportSales() As Decimal _
Implements ISecureService.ReportSales
In this code, you are using the PrincipalPermission attribute to demand that users belong to a specific role in order to execute the code in the ReportSales method. For the purposes of this demo, use a role to which you do not belong.
If you call this method and are not in the Windows built-in BackupOperators group, a security exception will occur. You will next trap for this in the client application. In the Solution Explorer, right-click the Form1 file and select View Code. Add the following to the top of the code file:
// C#
using System.ServiceModel.Security;
' Visual Basic
Imports System.ServiceModel.Security
Add the following code to the reportSalesButton_Click method:
// C#
try
{
MessageBox.Show(String.Format(
"Today's sales are {0:C}", proxy.ReportSales()));
}
catch (SecurityAccessDeniedException securityEx)
{
MessageBox.Show(securityEx.Message);
}
catch (FaultException faultEx)
{
MessageBox.Show(faultEx.Message);
}
' Visual Basic
Try
MessageBox.Show(String.Format( _
"Today's sales are {0:C}", proxy.ReportSales))
Catch securityEx As SecurityAccessDeniedException
MessageBox.Show(securityEx.Message)
Catch faultEx As FaultException
MessageBox.Show(faultEx.Message)
Save your changes and build the solution. Press CTRL+F5 to run the application in release mode. Confirm that the console window displays that the services are hosted. In the form, select either binding and click Report sales. The application should display the message shown in Figure 3.
Figure 3. You are not authorized to see the sales number.
Click OK to dismiss the message. Close the form and then press any key in the console window to stop the service.
You just used the PrincipalPermission attribute at the operation level to prevent unauthorized clients from calling a method. You might want more fine-grained control. You will next modify your service code to allow all clients to call the ReportSales method. However, only authorized users will be able to see the actual sales number.
Return to the SecureService code file. Add the following to the top of the code file:
// C#
using System.Security;
using System.Security.Principal;
' Visual Basic
Imports System.Security
Imports System.Security.Principal
Make the following changes to the ReportSales method:
// C#
//[PrincipalPermission(SecurityAction.Demand,
// Role="BUILTIN\\BackupOperators")]
public decimal ReportSales()
{
var currentUser = new WindowsPrincipal((WindowsIdentity)
System.Threading.Thread.CurrentPrincipal.Identity);
if (currentUser.IsInRole(WindowsBuiltInRole.BackupOperator))
{
return 10000M;
}
else
{
return -1M;
}
}
' Visual Basic
'<PrincipalPermission(SecurityAction.Demand, _
' Role:="BUILTIN\\BackupOperators")> _
Public Function ReportSales() As Decimal _
Implements ISecureService.ReportSales
Dim currentUser As New WindowsPrincipal(CType( _
Threading.Thread.CurrentPrincipal.Identity, WindowsIdentity))
If currentUser.IsInRole(WindowsBuiltInRole.BackupOperator) Then
Return 10000D
Else
Return -1D
End If
End Function
The WindowsPrincipal class represents the security context of a Windows user. You can use the IsInRole method of that class to determine if a user has a particular role. The code above casts the thread’s identity to a WindowsIdentity object and passes that to the WindowsPrincipal constructor.
You will next modify the client application to display a more user-friendly message if the user is not authorized to view sales numbers. Return to the Form1 code file and make the following changes to the reportSalesButton_Click method:
// C#
try
{
decimal sales = proxy.ReportSales();
if (sales != -1)
{
MessageBox.Show(String.Format("Today's sales are {0:C}", sales)); }
else
{
MessageBox.Show(String.Format(
"You are not authorized to view sales numbers"));
}
}
catch (SecurityAccessDeniedException securityEx)
' Visual Basic
Try
Dim sales As Decimal = proxy.ReportSales
If sales <> -1 Then
MessageBox.Show(String.Format( _
"Today's sales are {0:C}", sales))
Else
MessageBox.Show( _
"You are not authorized to view sales numbers")
End If
Catch securityEx As SecurityAccessDeniedException
Save your changes and build the solution. Press CTRL+F5 to run the application in release mode. Confirm that the console window displays that the services are hosted. In the form, select either binding and click Report sales. The application should display the message shown in Figure 4.
Figure 4. You are not authorized to see the sales number.
Click OK to dismiss the message. Close the form and then press any key in the console window to stop the service.
Conclusion
In this tutorial, you first saw a high-level overview of WCF security. You reviewed the difference between transport security and message security and you reviewed the options each provides for authenticating users who call a service.
You then saw how to use authentication and authorization in the scenario where you are self-hosting a WCF service on the corporate network. You saw that if you use either the NetTcpBinding or WSHttpBinding binding, the client automatically passes to the service the credentials of the logged in user. You then saw how to limit usage of a service method based on the role of the user.
In the second part of this two-part tutorial, you will see how to use authentication and authorization in two additional scenarios: an internal Web-hosted service and a public Web-hosted service.
For additional information on securing WCF services, you should review the Microsoft patterns & practices Improving Web Services Security Guide at https://www.codeplex.com/WCFSecurityGuide.
About the Author
Robert Green is a developer, writer, and trainer. He is a senior consultant with MCW Technologies. Robert is a Visual Studio Tools for the Office system MVP and is a co-author of AppDev courseware for Microsoft Visual Basic, Microsoft Visual C#, LINQ and Microsoft Windows Workflow Foundation. Before joining MCW, Robert worked at Microsoft as a Product Manager and also a Program Manager.