In-Depth
Security Considerations and Best Practices for WCF 4 Apps
The Windows Communication Foundation (WCF) framework can be vulnerable to phishing and other attacks. Learn how to protect it with these tactics.
Windows Communication Foundation (WCF) is used to build, deploy and execute connected systems. WCF provides a secure, reliable, scalable messaging framework that can work over any protocol in any network. However, you need to secure your WCF service from phishing attacks when passing sensitive information through the network. Messages transferred through the transport channel should be secure enough so that a hacker can't intercept them and change their contents. Security is a major aspect of real-time WCF services that transmit sensitive and confidential information over the wire.
This article discusses the basics of WCF, WCF bindings, security considerations and best practices for using WCF services.
Binding in WCF is used to specify how clients can communicate with the service. Here, I'll explore WCF bindings and why and how they're used.
Note: To work with the code samples presented in this article, you should have Visual Studio 2010 or higher installed.
Securing Your WCF Service
The basic concepts in WCF security are:
- Authentication -- identification of the message sender and the message receiver
- Authorization -- which features can be accessed
- Integrity -- before being transmitted over the wire, messages should be digitally signed to ensure they haven't been altered
- Confidentiality -- messages should be encrypted to maintain confidentiality
Authentication is the process of validating the identity of clients and services. WCF provides the following types of credentials when working in transport-level security:
- None -- the service doesn't validate the client
- Basic -- the client passes the user's credentials to the service, which is then validated
- Windows -- the client uses a token that represents the identity of the logged-in user
- Certificate -- the client and the service are authenticated using certificates
- NT LAN Manager (NTLM) -- the service uses an SSL certificate and validates the client is using Windows accounts (note that this works only with HTTP)
WCF supports the following credential types for message-level security:
- UserName
- Certificate
- IssueToken
- Windows
- None
Authorization works only for authenticated clients. Once a client has been authenticated, authorization determines the operations it can access. There are three supported types of authorization in WCF:
- Role-based -- access to resources is restricted based on the authenticated user's role
- Resource-based -- WCF services are secured using access control lists (ACLs)
- Identity-based -- claims-based security with token authentication provides authorization
To secure a WCF service, you need to define a security policy and then specify a service configuration to enforce it.
WCF supports the following security modes:
- None -- messages aren't secured
- Transport -- messages are secured using transport security
- Message -- messages are secured using message security
- TransportWithMessageCredential -- message protection and authorization occur at the transport level, and credentials are passed along with the message
- TransportCredentialOnly -- credentials are passed at the transport level, but the message isn't encrypted (this mode works with BasicHttpBinding)
- Both -- messages are secured using transport-level and message-level security
Note that WCF also provides support for mixed-mode security, which is a mix of transport and message security. In this case, confidentiality, integrity, and authentication are provided at the transport level and client authentication is provided at the message level.
Transport-Level Security
In transport-level security, the user credentials (which are transport-dependent) are passed through the transport layer. Transport security provides point-to-point security between two endpoints (server and the client). Transport-level security uses transport protocols such as Transmission Control Protocol (TCP), HTTP, Microsoft Message Queuing (MSMQ) and so on, with their own security mechanisms.
You can enable transport security using the Security attribute in your configuration file:
<bindings>
<wsHttpBinding>
<binding name="TransportSecurity">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
Message-Level Security
In message-level security, the user credentials and claims are encrypted and encapsulated in the messages using the Web Services Security (WS-Security) specification. This promotes better flexibility, as you can use any transport mechanism for message transfer. In other words, message-level security is independent of the transport protocol. Message-level security makes use of the WS-Security specification to secure messages and ensure confidentiality, integrity, and authentication at the SOAP message level -- not at the transport level.
Here's how to specify UserName credentials with a message using wsHttpBinding:
<wsHttpBinding>
<binding name = "wsHttp">
<security mode = "Message">
<message clientCredentialType = "UserName"/>
</security>
</binding>
</wsHttpBinding>
The following code snippet illustrates how message attributes can be used to specify certificate validation:
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpointBinding">
<security>
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
Transport-level security is faster than message-level security, because message-level security encrypts and signs every message. However, transport-level security is protocol-dependent and has limited security support. Message-level security, while slower, provides end-to-end security.
Table 1 lists the various types of bindings in WCF and their supported modes.
Binding Type |
Transport Mode |
Message Mode |
BasicHttpBinding |
Yes |
Yes |
WSHttpBinding |
Yes |
Yes |
WSDualHttpBinding |
No |
Yes |
NetTcpBinding |
Yes |
Yes |
NetNamedPipeBinding |
Yes |
No |
NetMsmqBinding |
Yes |
Yes |
MsmqIntegrationBinding |
Yes |
No |
wsFederationHttpBinding |
No |
Yes |
Table 1. Windows Communication Foundation bindings, and which modes are supported for each.
|
When using basicHttpBinding or WsHttpBinding in transport-security mode, the available client credential types include:
- None
- Basic
- Digest
- NTLM
- Windows
- Certificate
- IssuedToken
The available client credential types for message mode include:
- None
- Windows
- UserName
- Certificate
- IssuedToken
Implementing Message Security
Consider the following ServiceContract that contains a method, GetUserInformation. Note that if the credentials supplied to this service method (as parameters) are valid, the operation contract returns an instance of the UserInfo class:
[ServiceContract]
public interface ISecurityService
{
[OperationContract]
[FaultContract(typeof(FaultDetails))]
UserInfo GetUserInformation(SecurityCredentials credential);
}
The code in Listing 1 shows the userInfo and SecurityCredentials MessageContract. While the former contains properties related to the user entity, the latter contains a token property and user ID property. This token would be validated in the GetUserInformation(SecurityCredentials credential) operation contract.
Listing 1. The userInfo and SecurityCredentials MessageContract.
[MessageContract]
public class UserInfo
{
[MessageBodyMemberAttribute(Order = 1, ProtectionLevel = ProtectionLevel.None)]
public Int32 UserID { get; set; }
[MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.Sign)]
public String UserName { get; set; }
[MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
public String Password { get; set; }
}
[MessageContract]
public class SecurityCredentials
{
[MessageBodyMemberAttribute(Order = 1, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
public String Token { get; set; }
[MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.None)]
public Int32 UserID { get; set; }
}
I'll also capture exceptions. To do this, I use the custom class FaultDetails and make use of FaultExceptions in the code. The FaultDetails message contract comprises two properties: Code and Message. While the first relates to the error code, the second contains the error message for an exception, as you'll see in Listing 2.
Listing 2. The FaultDetails message contract contains Code and Message properties.
[MessageContract]
public class FaultDetails
{
[MessageBodyMemberAttribute(Order = 1, ProtectionLevel = ProtectionLevel.None)]
public string Code
{
get;
set;
}
[MessageBodyMemberAttribute(Order = 2, ProtectionLevel = ProtectionLevel.None)]
public string Message
{
get;
set;
}
}
The complete implementation of the ISecurityService ServiceContract is shown in Listing 3.
Listing 3. The complete implementation of the ISecurityService ServiceContract.
public class SecurityService : ISecurityService
{
public UserInfo GetUserInformation(SecurityCredentials credential)
{
FaultDetails faultObject = new FaultDetails();
faultObject.Code = "F001";
faultObject.Message = "Invalid token...";
if (credential.Token.Equals("123@#123"))
return new UserInfo { UserID = 1, UserName = "Joydip", Password = "Joydip1@3" };
else
throw new FaultException<FaultDetails>(
faultObject, new FaultReason("Invalid token..."));
}
}
To consume this service, create a client project (for example, a Web project, which would be the service consumer) and add a service reference. You'll need to select the SecurityService and add it as a service reference; then you're all set to consume it. The code in Listing 4 illustrates how to consume the SecurityService. Notice that I've used an ASP.NET Web application as the service consumer.
Listing 4. How to consume the SecurityService.
try
{
ChannelFactory<ISecurityService> factory =
new ChannelFactory<ISecurityService>("WSHttpBinding_ISecurityService",
new EndpointAddress("http://localhost/Demo/SecurityService.svc"));
ISecurityService proxy = factory.CreateChannel();
SecurityCredentials credential = new SecurityCredentials();
credential.Token = "123@123";
credential.UserID = 1;
UserInfo userObj = proxy.GetUserInformation(credential);
Response.Write(userObj.UserName);
}
catch (FaultException<FaultDetails> fe)
{
Response.Write(fe.Message);
}
In Listing 4, a transport channel was created using ChannelFactory. I then created an instance of the SecurityCredentials class and populated its members with the necessary credential information to validate the user. Note how I used Token and UserID -- the GetUserInformation operation contract was invoked and the credential object was passed to it as a parameter. If the supplied credential is valid, an instance of UserInfo class is returned; otherwise, an exception is thrown and handled using the FaultException class. (The complete source code can be found at the download link at the top of this article.)
If code execution stops at each exception, you can remove this behavior. Switch to Tools | Options in Visual Studio and un-check the options "Enable the exception assistant" and "Enable Just My Code" (Managed Only). Figure 1 shows an example of this options dialog.
Implementing Certificate Security
Now, to secure a WCF service using certificates and to execute the service over HTTPS in the context of IIS, you can follow these steps:
- Create a WCF service and save it with a name.
- Implement the Service Contract and one or more Operation Contract methods on your service.
- Enable transport security for the service:
<bindings>
<wsHttpBinding>
<binding name="TransportSecurity">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
- Specify HTTPS configuration for your service:
<service name="DemoService" behaviorConfiguration="DemoServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="https://localhost/DemoService.svc" binding="wsHttpBinding"
bindingConfiguration="TransportSecurity" contract="IDemoService"/>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
</service>
- Update the serviceMetadata tag in the service configuration file -- change it from httpGetEnabled to httpsGetEnabled:
<serviceBehaviors>
<serviceMetadata httpsGetEnabled="true"/>
</serviceBehaviors>
- Your service configuration should also specify the clientCredentialType to be used:
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpointBinding">
<security>
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
- Now, compile the service application and host it in IIS, with HTTPS enabled.
- Create certificates (one for the server and the other for the client) using the makecert.exe tool. Open the Visual Studio Command Prompt and type the following commands:
makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=MyWCFServer -sky exchange -pe
makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=MyWCFClient -sky exchange –pe
This creates two certificates: one for the WCF server application and one for the client application (the service consumer). More information on this tool is available on the DigitallyCreated blog.
- Next, assign this certificate to your IIS Web site using the Server Certificate option in IIS, or open the Microsoft Management Console (MMC) and add the certificates using the "Add or Remove Snap-ins" dialog.
- Specify the certificate information in the configuration file for the service.
- You should also enable HTTPS mode and anonymous access in IIS.
- Create a client application to consume the service you created.
- Add a service reference to the service created earlier.
- Specify the needed configuration details in the client (service consumer) configuration file for the certificate you created earlier.
Now you can use this service consumer to start invoking the service methods.
A Solid, Secure Foundation
WCF provides a platform for the unification of a number of technologies under a single umbrella. It can be used to design and implement platform-independent, extendable and scalable services.
You can implement authentication, authorization, certificates or token-based security to secure your confidential data while using WCF services. You can secure the data passed through the wire using either transport- or message-level security, providing plentiful options. Your choice of options should be driven by your application's needs, the underlying platform, the transport mechanism in use and the security services provider for which you select.