Programming WCF Security
This topic describes the fundamental programming tasks used to create a secure Windows Communication Foundation (WCF) application. This topic covers only authentication, confidentiality, and integrity, collectively known as transfer security. This topic does not cover authorization (the control of access to resources or services); for information on authorization, see Authorization.
Note
For a valuable introduction to security concepts, especially in regard to WCF, see the set of patterns and practices tutorials on MSDN at Scenarios, Patterns, and Implementation Guidance for Web Services Enhancements (WSE) 3.0.
Programming WCF security is based on three steps setting the following: the security mode, a client credential type, and the credential values. You can perform these steps either through code or configuration.
Setting the Security Mode
The following explains the general steps for programming with the security mode in WCF:
Select one of the predefined bindings appropriate to your application requirements. For a list of the binding choices, see System-Provided Bindings. By default, nearly every binding has security enabled. The one exception is the BasicHttpBinding class (using configuration, the <basicHttpBinding>).
The binding you select determines the transport. For example, WSHttpBinding uses HTTP as the transport; NetTcpBinding uses TCP.
Select one of the security modes for the binding. Note that the binding you select determines the available mode choices. For example, the WSDualHttpBinding does not allow transport security (it is not an option). Similarly, neither the MsmqIntegrationBinding nor the NetNamedPipeBinding allows message security.
You have three choices:
Transport
Transport security depends on the mechanism that the binding you have selected uses. For example, if you are using
WSHttpBinding
then the security mechanism is Secure Sockets Layer (SSL) (also the mechanism for the HTTPS protocol). Generally speaking, the main advantage of transport security is that it delivers good throughput no matter which transport you are using. However, it does have two limitations: The first is that the transport mechanism dictates the credential type used to authenticate a user. This is a drawback only if a service needs to interoperate with other services that demand different types of credentials. The second is that, because the security is not applied at the message level, security is implemented in a hop-by-hop manner rather than end-to-end. This latter limitation is an issue only if the message path between client and service includes intermediaries. For more information about which transport to use, see Choosing a Transport. For more information about using transport security, see Transport Security Overview.Message
Message security means that every message includes the necessary headers and data to keep the message secure. Because the composition of the headers varies, you can include any number of credentials. This becomes a factor if you are interoperating with other services that demand a specific credential type that a transport mechanism can't supply, or if the message must be used with more than one service, where each service demands a different credential type.
For more information, see Message Security.
TransportWithMessageCredential
This choice uses the transport layer to secure the message transfer, while every message includes the rich credentials other services need. This combines the performance advantage of transport security with the rich credentials advantage of message security. This is available with the following bindings: BasicHttpBinding, WSFederationHttpBinding, NetPeerTcpBinding, and WSHttpBinding.
If you decide to use transport security for HTTP (in other words, HTTPS), you must also configure the host with an SSL certificate and enable SSL on a port. For more information, see HTTP Transport Security.
If you are using the WSHttpBinding and do not need to establish a secure session, set the EstablishSecurityContext property to
false
.A secure session occurs when a client and service create a channel using a symmetric key (both client and server use the same key for the length of a conversation, until the dialog is closed).
Setting the Client Credential Type
Select a client credential type as appropriate. For more information, see Selecting a Credential Type. The following client credential types are available:
Windows
Certificate
Digest
Basic
UserName
NTLM
IssuedToken
Depending on how you set the mode, you must set the credential type. For example, if you have selected the wsHttpBinding
, and have set the mode to "Message," then you can also set the clientCredentialType
attribute of the Message element to one of the following values: None
, Windows
, UserName
, Certificate
, and IssuedToken
, as shown in the following configuration example.
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="myBinding">
<security mode="Message"/>
<message clientCredentialType="Windows"/>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
Or in code:
WSHttpBinding b = new WSHttpBinding();
b.Name = "myBinding";
b.Security.Mode = SecurityMode.Message;
b.Security.Message.ClientCredentialType=MessageCredentialType.Windows;
Dim b As New WSHttpBinding()
b.Name = "myBinding"
b.Security.Mode = SecurityMode.Message
b.Security.Message.ClientCredentialType = MessageCredentialType.Windows
Setting Service Credential Values
Once you select a client credential type, you must set the actual credentials for the service and client to use. On the service, credentials are set using the ServiceCredentials class and returned by the Credentials property of the ServiceHostBase class. The binding in use implies the service credential type, the security mode chosen, and the type of the client credential. The following code sets a certificate for a service credential.
// Create the binding for an endpoint.
NetTcpBinding b = new NetTcpBinding();
b.Security.Mode = SecurityMode.Message;
// Create the ServiceHost for a calculator.
Uri baseUri = new Uri("net.tcp://MachineName/tcpBase");
Uri[] baseAddresses = new Uri[] { baseUri };
ServiceHost sh = new ServiceHost(typeof(Calculator), baseAddresses);
// Add an endpoint using the binding and a new address.
Type c = typeof(ICalculator);
sh.AddServiceEndpoint(c, b, "MyEndpoint");
// Set a certificate as the credential for the service.
sh.Credentials.ServiceCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindBySubjectName,
"client.com");
try
{
sh.Open();
Console.WriteLine("Listening....");
Console.ReadLine();
sh.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("A communication error occurred: {0}", ce.Message);
Console.WriteLine();
}
catch (System.Exception exc)
{
Console.WriteLine("An unforeseen error occurred: {0}", exc.Message);
Console.ReadLine();
}
' Create the binding for an endpoint.
Dim b As New NetTcpBinding()
b.Security.Mode = SecurityMode.Message
' Create the ServiceHost for a calculator.
Dim baseUri As New Uri("net.tcp://MachineName/tcpBase")
Dim baseAddresses() As Uri = {baseUri}
Dim sh As New ServiceHost(GetType(Calculator), baseAddresses)
' Add an endpoint using the binding and a new address.
Dim c As Type = GetType(ICalculator)
sh.AddServiceEndpoint(c, b, "MyEndpoint")
' Set a certificate as the credential for the service.
sh.Credentials.ServiceCertificate.SetCertificate( _
StoreLocation.LocalMachine, _
StoreName.My, _
X509FindType.FindBySubjectName, _
"contoso.com")
Try
sh.Open()
Console.WriteLine("Listening....")
Console.ReadLine()
sh.Close()
Catch ce As CommunicationException
Console.WriteLine("A communication error occurred: {0}", ce.Message)
Console.WriteLine()
Catch exc As System.Exception
Console.WriteLine("An unforeseen error occurred: {0}", exc.Message)
Console.ReadLine()
End Try
Setting Client Credential Values
On the client, set client credential values using the ClientCredentials class and returned by the ClientCredentials property of the ClientBase<TChannel> class. The following code sets a certificate as a credential on a client using the TCP protocol.
// Create a NetTcpBinding and set its security properties. The
// security mode is Message, and the client must be authenticated with
// Windows. Therefore the client must be on the same Windows domain.
NetTcpBinding b = new NetTcpBinding();
b.Security.Mode = SecurityMode.Message;
b.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
// Set a Type variable for use when constructing the endpoint.
Type c = typeof(ICalculator);
// Create a base address for the service.
Uri tcpBaseAddress =
new Uri("net.tcp://machineName.Domain.Contoso.com:8036/serviceName");
// The base address is in an array of URI objects.
Uri[] baseAddresses = new Uri[] { tcpBaseAddress };
// Create the ServiceHost with type and base addresses.
ServiceHost sh = new ServiceHost(typeof(CalculatorClient), baseAddresses);
// Add an endpoint to the service using the service type and binding.
sh.AddServiceEndpoint(c, b, "");
sh.Open();
string address = sh.Description.Endpoints[0].ListenUri.AbsoluteUri;
Console.WriteLine("Listening @ {0}", address);
Console.WriteLine("Press enter to close the service");
Console.ReadLine();
' Create a NetTcpBinding and set its security properties. The
' security mode is Message, and the client must be authenticated with
' Windows. Therefore the client must be on the same Windows domain.
Dim b As New NetTcpBinding()
b.Security.Mode = SecurityMode.Message
b.Security.Message.ClientCredentialType = MessageCredentialType.Windows
' Set a Type variable for use when constructing the endpoint.
Dim c As Type = GetType(ICalculator)
' Create a base address for the service.
Dim tcpBaseAddress As New Uri("net.tcp://machineName.Domain.Contoso.com:8036/serviceName")
' The base address is in an array of URI objects.
Dim baseAddresses() As Uri = {tcpBaseAddress}
' Create the ServiceHost with type and base addresses.
Dim sh As New ServiceHost(GetType(CalculatorClient), baseAddresses)
' Add an endpoint to the service using the service type and binding.
sh.AddServiceEndpoint(c, b, "")
sh.Open()
Dim address As String = sh.Description.Endpoints(0).ListenUri.AbsoluteUri
Console.WriteLine("Listening @ {0}", address)
Console.WriteLine("Press enter to close the service")
Console.ReadLine()