Export (0) Print
Expand All

Security

This chapter is excerpted from Programming WCF Services, Second Edition: Building Service Oriented Applications with Windows Communication Foundation by Juval Lowy, published by O'Reilly Media

There are several aspects pertaining to secure interaction between a client and a service. As in traditional client/server and component-oriented applications, the service needs to authenticate its callers and often also authorize the callers before executing sensitive operations. In addition, regardless of the technology, when securing a service (and its clients) as in any distributed system, you need to secure the messages while they are en route from the client to the service. Once the messages arrive securely and are authenticated and authorized, the service has a number of options regarding the identity it uses to execute the operation. This chapter will explore these classic security aspects-authentication, authorization, transfer security, and identity management-as well as something more abstract, which I call overall security policy: that is, your own personal and your company's (or customer's) approach to and mindset regarding security. This chapter starts by defining the various aspects of security in the context of WCF and the options available to developers when it comes to utilizing WCF and .NET security. Then, it explains how to secure the canonical and prevailing types of applications. Finally, I will present my declarative security framework, which vastly reduces the complexity of the WCF security programming model by eliminating the need to understand and tweak the many details of WCF security.

Authentication

Authentication is the act of verifying that the caller of a service is indeed who that caller claims to be. While authentication is typically referred to in the context of verification of the caller, from the client perspective there is also a need for service authentication; that is, assuring the client that the service it calls really is the service it intends to call. This is especially important with clients who call over the Internet, because if a malicious party subverts the client's DNS service, it could hijack the client's calls. WCF offers various authentication mechanisms:

No authentication

The service does not authenticate its callers, and virtually all callers are allowed.

Windows authentication

The service typically uses Kerberos when a Windows Domain Server is available, or NTLM when deployed in a workgroup configuration. The caller provides the service with its Windows credentials (such as a ticket or a token) and the service authenticates that against Windows.

Username and password

The caller provides the service with a username and a password. The service then uses these credentials against some kind of credentials store, such as Windows accounts or a custom credentials store (such as a dedicated database).

X509 certificate

The client identifies itself using a certificate. Typically, that certificate is known in advance to the service. The service looks up the certificate on the host side and validates it, thus authenticating the client. Alternatively, the service may implicitly trust the issuer of the certificate and hence the client presenting it.

Custom mechanism

WCF allows developers to replace the built-in authentication mechanisms with any protocol and credential type, such as using biometrics. These custom solutions are beyond the scope of this book.

Issued token

The caller and the service can both rely on a secure token service to issue the client a token that the service recognizes and trusts. Such a service is typically federated and encapsulates the act of authenticating and securing the call. Windows CardSpace is an example of such a secure token service. However, federated security and CardSpace are beyond the scope of this book.

Authorization is concerned with what the caller is allowed to do: typically, which operations the client is allowed to invoke on the service. Authorizing of the caller is done under the assumption that the caller is indeed who the caller claims to be-in other words, authorization is meaningless without authentication. For authorization, the service typically relies on some kind of credentials store, where callers are mapped to logical roles. When authorizing an operation, the operation declares or explicitly demands that only certain roles can access it, and the service needs to look up the caller's role or roles from the store and verify that the caller is a member of the requested roles. Out of the box, WCF supports two credentials stores: the service can use Windows groups (and accounts) for authorization, or it can use an ASP.NET provider (such as the SQL Server provider) to store user accounts and roles. WCF also supports custom role repositories, but I have found that the easiest option by far for implementing a custom store is to implement a custom ASP.NET provider. This chapter will address the ASP.NET providers at length later.

Tip
WCF offers an elaborate and extensible infrastructure for authenticating and authorizing the caller based on a set of claims contained in the message. However, discussion of this mechanism is beyond the scope of this book.

Both authentication and authorization deal with two local aspects of security-if (and to what extent) to grant access to the caller once the service has received the message. In this respect, WCF services are not much different from traditional client/server classes. However, both authentication and authorization are predicated on secure delivery of the message itself. The transfer of the message from the client to the service has to be secure, or both authentication and authorization are moot. There are three essential aspects to transfer security, and all three aspects must be enforced to provide for secure services. Message integrity deals with how to ensure that the message itself is not tampered with en route from the client to the service. A malicious party or intermediary could, in practice, intercept the message and modify its content; for example, altering the account numbers in the case of a transfer operation in a banking service. Message privacy deals with ensuring the confidentiality of the message, so that no third party can even read the contents of the message. Privacy complements integrity. Without it, even if the malicious party does not tamper with the message, that party can still cause harm by gleaning sensitive information (again, such as account numbers) from the message content. Finally, transfer security must provide for mutual authentication, which deals with assuring the client that only the proper service is able to read the content of its message-in other words, that the client connects to the correct service. Once the credentials in the message are received, the service must authenticate those credentials locally. The mutual authentication mechanism also needs to detect and eliminate replay attacks and denial of service (DOS) attacks. In a replay attack, a malicious party records a valid message from the wire and later sends that valid message back to the service. With a DOS attack, a malicious party floods the service with messages (either valid messages or bogus invalid messages) at such a frequency as to degrade the service's availability.

Transfer Security Modes

WCF supports five different ways of accomplishing the three aspects of transfer security. Choosing the correct transfer security mode is perhaps the prime decision to be made in the context of securing a service. The five transfer security modes are None, Transport security, Message security, Mixed, and Both.

None transfer security mode

As its name implies, the None transfer security mode has transfer security completely turned off-in fact, all aspects of WCF security are turned off. No client credentials are provided to the service, and the message itself is wide open to any malicious party to do with it as it pleases. Obviously, setting transfer security to None is highly inadvisable.

Transport transfer security mode

When configured for Transport security, WCF uses a secure communication protocol. The available secure transports are HTTPS, TCP, IPC, and MSMQ. Transport security encrypts all communication on the channel and thus provides for integrity, privacy, and mutual authentication. Integrity is provided because without knowing the encryption key, any attempt to modify the message will corrupt it so that it will become useless. Privacy is provided because no party other than the recipient can see the content of the message. Mutual authentication is supported because only the intended recipient of the message can read it; the client need not be concerned with message rerouting to malicious endpoints, as those will not be able to use the message. Once the message is decrypted, the service can read the client's credentials and authenticate the client.

Transport security requires the client and the service to negotiate the details of the encryption, but that is done automatically as part of the communication protocol in the respective binding. Transport security can benefit from hardware acceleration done on the network card so as to avoid burdening the host machine's CPU with the encryption and decryption of the messages. Hardware acceleration obviously caters to high throughput, and it may even make the security overhead unnoticeable. Transport security is the simplest way of achieving transfer security, and the most performant option. Its main downside is that it can only guarantee transfer security point-to-point, meaning when the client connects directly to the service. Having multiple intermediaries between the client and the service renders Transport security questionable, as those intermediaries may not be secure. Consequently, Transport security is typically used only by intranet applications, where you can ensure a single hop between the client and the service in a controlled environment.

Tip
When configuring any of the HTTP bindings for Transport security, WCF verifies at the service load time that the corresponding address on the endpoint uses HTTPS rather than mere HTTP.

Message transfer security mode

The Message transfer security mode simply encrypts the message itself. By encrypting the message, you gain integrity and privacy and enable mutual authentication, for the same reason that Transport security provides these features when the communication channel is encrypted. However, encrypting the message rather than the transport enables the service to communicate securely over nonsecure transports, such as HTTP. Because of that, Message security provides for end-to-end security, regardless of the number of intermediaries involved in transferring the message and regardless of whether or not the transport is secure. In addition, Message security is based on a set of industry standards designed both for interoperability and for thwarting common attacks such as replay and DOS attacks, and the support WCF offers for it is both rich and extensible. The downside of Message security is that it may introduce call latency due to its inherent overhead. Message security is typically used by Internet applications, where the call patterns are less chatty and the transport is not necessarily secure.

Mixed transfer security mode

The Mixed transfer security mode uses Transport security for message integrity and privacy as well as service authentication, and it uses Message security for securing the client's credentials. The Mixed mode tries to combine the advantages of both Transport and Message security by benefiting from the secure transport and even hardware acceleration offered by Transport security to cater to high throughput, and from the extensibility and richer types of client credentials offered by Message security. The downside of the Mixed mode is that it is only secure point-to-point, as a result of the use of Transport security. Application developers rarely need to use the Mixed mode, but it is available for advanced cases.

Both transfer security mode

As its name implies, the Both transfer security mode uses both Transport security and Message security. The message itself is secured using Message security, and then it is transferred to the service over a secure transport. The Both mode maximizes security, yet it may be overkill for most applications (with the exception perhaps of disconnected applications, where the additional latency it introduces will go unnoticed).

Transfer Security Mode Configuration

Configuring the transfer security mode is done in the binding, and both the client and the service must use the same transfer security mode and, of course, comply with its requirements. Like any other binding configuration, you can configure transfer security either programmatically or administratively, in a config file. All the common bindings offer a construction parameter indicating the transfer security mode, and all bindings offer a Security property with a Mode property identifying the configured mode using a dedicated enumeration. As shown in Table 10.1, "Bindings and transfer security modes", not all bindings support all transfer security modes: the supported modes are driven by the target scenarios for the binding.

Table 10.1. Bindings and transfer security modes

Name

None

Transport

Message

Mixed

Both

BasicHttpBinding

Yes (default)

Yes

Yes

Yes

No

NetTcpBinding

Yes

Yes (default)

Yes

Yes

No

NetNamedPipeBinding

Yes

Yes (default)

No

No

No

WSHttpBinding

Yes

Yes

Yes (default)

Yes

No

WSDualHttpBinding

Yes

No

Yes (default)

No

No

NetMsmqBinding

Yes

Yes (default)

Yes

No

Yes


The intranet bindings (NetTcpBinding, NetNamedPipeBinding, and NetMsmqBinding) all default to Transport security. Thus, no special programming is required on behalf of the service or client developer. The reason is that on the intranet calls are typically point-to-point, and Transport security yields the best performance. However, the intranet bindings can also be configured for the None transfer mode; that is, they can be used on the same transport protocol, only without security. The NetNamedPipeBinding supports only None and Transport security-there is no sense in using Message security over IPC, since with IPC there is always exactly one hop from the client to the service. Also note that only the NetMsmqBinding supports the Both mode.

The Internet bindings all default to Message security, to enable them to be used over nonsecure transports (that is, HTTP) and to accommodate multiple hops and intermediaries. Note that while the WSHttpBinding can be configured for Transport security, the WSDualHttpBinding cannot. The reason is that this binding uses a separate HTTP channel to connect the service to the callback client, and that channel to the client-hosted callback object cannot easily be made to use HTTPS, unlike a service that is likely to be hosted in a real web server.

With one noticeable exception, all of the WCF bindings are configured with some kind of transfer security and are therefore secure by default. Only the BasicHttpBinding defaults to having no security. The reason is that the basic binding is designed to make a WCF service look like a legacy ASMX service, and ASMX is unsecured by default. That said, you can and should configure the BasicHttpBinding to use a different transfer security mode, such as Message security.

Specific binding configurations

The BasicHttpBinding uses the BasicHttpSecurityMode enum for transfer mode configuration. The enum is available via the Mode property of the Security property of the binding:

public enum BasicHttpSecurityMode
{
   None,
   Transport,
   Message,
   TransportWithMessageCredential,
   TransportCredentialOnly
}
public sealed class BasicHttpSecurity
{
   public BasicHttpSecurityMode Mode
   {get;set;}
   //More members
}
public class BasicHttpBinding : Binding,...
{
   public BasicHttpBinding(  );
   public BasicHttpBinding(BasicHttpSecurityMode securityMode);
   public BasicHttpSecurity Security
   {get;}
   //More members
}

Security is of the type BasicHttpSecurity. One of the constructors of BasicHttpBinding takes the BasicHttpSecurityMode enum as a parameter. To secure the basic binding for Message security, you can either construct it secured or set the security mode post-construction. Consequently, in Example 10.1, "Programmatically securing the basic binding"binding1 and binding2 are equivalent.

Example 10.1. Programmatically securing the basic binding

BasicHttpBinding binding1 = new BasicHttpBinding(BasicHttpSecurityMode.Message);

BasicHttpBinding binding2 = new BasicHttpBinding(  );
binding2.Security.Mode = BasicHttpSecurityMode.Message;

Instead of programmatic settings, you can use a config file, as in Example 10.2, "Administratively securing the basic binding".

Example 10.2. Administratively securing the basic binding

<bindings>
   <basicHttpBinding>
      <binding name = "SecuredBasic">
         <security mode = "Message"/>
      </binding>
   </basicHttpBinding>
</bindings>

The rest of the bindings all use their own enumerations and dedicated security classes, yet they are configured just as in Example 10.1, "Programmatically securing the basic binding" and Example 10.2, "Administratively securing the basic binding". For example, the NetTcpBinding and the WSHttpBinding use the SecurityMode enum, defined as:

public enum SecurityMode
{
   None,
   Transport,
   Message,
   TransportWithMessageCredential //Mixed
}

These bindings offer a matching construction parameter and a matching Security property.

The NetNamedPipeBinding uses the NetNamedPipeSecurityMode enum, which supports only the None and Transport security modes:

public enum NetNamedPipeSecurityMode
{
   None,
   Transport
}

The WSDualHttpBinding uses the WSDualHttpSecurityMode enum, which supports only the None and Message security modes:

public enum WSDualHttpSecurityMode
{
   None,
   Message
}

The NetMsmqBinding uses the NetMsmqSecurityMode enum:

public enum NetMsmqSecurityMode
{
   None,
   Transport,
   Message,
   Both
}

NetMsmqSecurityMode is the only enum that offers the Both transfer mode.

The reason that almost every common binding has its own dedicated enum for the security mode is that the designers of WCF security opted for increased safety at the expense of overall complexity. They could have defined just a single all-inclusive enum with values corresponding to the five possible transfer security modes, but then it would have been possible at compile time to assign invalid values, such as Message security for the NetNamedPipeBinding or Transport security for the WSDualHttpBinding. Opting for specialized enums makes configuring security less error-prone, yet there are more moving parts to come to terms with.

Transport Security and Credentials

WCF lets you select from a number of possible client credential types. For example, the client can identify itself using a classic username and password, or a Windows security token. Windows credentials can then be authenticated using NTLM or Kerberos, when available. Alternatively, the client can use an X509 certificate, or choose to provide no credentials at all and be anonymous. When configuring transfer security for Transport security, however, not all bindings support all client credential types, as shown in Table 10.2, "Bindings and Transport security client credentials".

Table 10.2. Bindings and Transport security client credentials

Name

None

Windows

Username

Certificate

BasicHttpBinding

Yes (default)

Yes

Yes

Yes

NetTcpBinding

Yes

Yes (default)

No

Yes

NetNamedPipeBinding

No

Yes (default)

No

No

WSHttpBinding

Yes

Yes (default)

Yes

Yes

WSDualHttpBinding

N/A

N/A

N/A

N/A

NetMsmqBinding

Yes

Yes (default)

No

Yes


Which types of credentials a binding supports is largely a product of the target scenario for which the binding is designed. For example, all of the intranet bindings default to Windows credentials since they are used in a Windows environment, and the BasicHttpBinding defaults to no credentials, just like a classic ASMX web service. The WSDualHttpBinding cannot use Transport security at all. The odd default is that of the WSHttpBinding, which defaults to Windows credentials to enable the binding to be used over Transport security with minimum effort out of the box.

Message Security and Credentials

When it comes to using Message transfer security, WCF lets applications use the same types of credentials as with Transport security, with the addition of the issued token credential type. Again, when configured for Message security not all bindings support all client credential types, as shown in Table 10.3, "Bindings and Message security client credentials".

Table 10.3. Bindings and Message security client credentials

Name

None

Windows

Username

Certificate

Issued token

BasicHttpBinding

No

No

No

Yes

No

NetTcpBinding

Yes

Yes (default)

Yes

Yes

Yes

NetNamedPipeBinding

N/A

N/A

N/A

N/A

N/A

WSHttpBinding

Yes

Yes (default)

Yes

Yes

Yes

WSDualHttpBinding

Yes

Yes (default)

Yes

Yes

Yes

NetMsmqBinding

Yes

Yes (default)

Yes

Yes

Yes


While it makes sense that all intranet bindings that support Message security default to Windows credentials, it is interesting to note that the Internet bindings (WSHttpBinding and WSDualHttpBinding) also default to Windows credentials, even though (as discussed later) Internet applications rarely use Windows credentials over HTTP. The reason for this default is to enable developers to securely use these bindings out of the box, in their correct transfer security mode, without resorting first to custom credentials stores.

Warning
The BasicHttpBinding supports username client credentials for Message security only when configured for Mixed mode. This may be a source of runtime validation errors, since the BasicHttpMessageCredentialType enum contains the BasicHttpMessageCredentialType.UserName value.

Identity management is the security aspect that deals with which security identity the client sends to the service and, in turn, what the service can do with the client's identity. Not only that, but when designing a service, you need to decide in advance which identity the service will execute under. The service can execute under its own identity; it can impersonate the client's identity (when applicable); or it can use a mixture of identities, alternating in a single operation between its own identity, the client's identity, or even a third identity altogether. Selecting the correct identity has drastic implications on the application's scalability and administration cost. In WCF, when enabled, the security identity flows down the call chain, and each service can find out who its caller is, regardless of the identity of the service.

To the traditional commonplace security aspects of authentication, authorization, transfer security, and identity management, I would like to add one that is less technical and conventional, but to me just as important: what is your business's approach, or even your personal approach, to security? That is, what is your security policy? I believe that in the vast majority of cases, applications simply cannot afford not to be secured. And while security carries with it performance and throughput penalties, these should be of no concern. Simply put, it costs to live. Paying the security penalty is an unavoidable part of designing and administering modern connected applications. Gone are the days when developers could afford not to care about security and deploy applications that relied on the ambient security of the target environment, such as physical security provided by employee access cards or firewalls.

Since most developers cannot afford to become full-time security experts (nor should they), the approach I advocate for overall security policy is simple: crank security all the way up until someone complains. If the resulting application performance and throughput are still adequate with the maximum security level, leave it at that level. Only if the resulting performance is inadequate should you engage in detailed threat analysis to find out what you can trade in security in exchange for performance. In my experience, you will rarely need to actually go this route; most developers should never need to compromise security this way.

The security strategies described in this chapter follow my overall security policy. WCF's overall approach to security is very much aligned with my own, and I will explicitly point out the few places it is not (and how to rectify it). With the noticeable exception of the BasicHttpBinding, WCF is secured by default, and even the BasicHttpBinding can easily be secured. All other WCF bindings by default authenticate all callers to the service and rely on transfer security.

Security is by far the most intricate area of WCF. The following list shows the elements that govern security in every WCF operation call:

  • Service contract

  • Operation contract

  • Fault contract

  • Service behavior

  • Operation behavior

  • Host configuration

  • Method configuration and code

  • Client-side behavior

  • Proxy configuration

  • Binding configuration

Each of the items in the list may have a dozen or more security-related properties. Obviously, there are an overwhelming number of possible combinations and permutations. In addition, not all combinations are allowed or supported, and not all allowed combinations make sense or are consistent; for example, while technically possible, it does not make sense to use a certificate for client credentials in a homogenous Windows intranet, much as it makes little sense to use Windows accounts in an Internet application. The solution I chose for this book is to focus on a few key scenarios (and slight variations of them) that address the security needs of the majority of applications today.

The scenarios are:

  • Intranet application

  • Internet application

  • Business-to-business application

  • Anonymous application

  • No security

I will demonstrate how to make each of these scenarios consistent and secure. In each scenario I will discuss how to support the security aspects of transfer security, authentication, authorization, and identity management. If you need an additional scenario, you can follow my analysis approach to derive the required security aspects and settings.

The characteristics of the intranet application are that both the clients and the service use WCF, and that they are deployed in the same intranet. The clients reside behind the firewall, and you can use Windows-based security for transfer security, authentication, and authorization. You can rely on Windows accounts and groups to store the client's credentials. The intranet scenario addresses a wide range of business applications, from finance to manufacturing to in-house IT applications. The intranet scenario is also the richest scenario of all in the options it offers developers for configuring security.

This section on the intranet scenario will define the terminology, techniques, and types used in the other scenarios.

Securing the Intranet Bindings

For the intranet scenario, you should use the intranet bindings: namely, NetTcpBinding, NetNamedPipeBinding, and NetMsmqBinding. You can rely on Transport mode for transfer security because the calls are invariably point-to-point. Conveniently, Transport security is the default transfer mode of the intranet bindings (see Table 10.1, "Bindings and transfer security modes"). You can also use the default for the client credentials type, which is Windows (see Table 10.2, "Bindings and Transport security client credentials"). You need to configure this on both the client and the service.

Transport security protection level

Each of the three intranet bindings has a configurable protection level, which is the master switch for Transport protection. The three protection levels are:

None

When configured for this protection level, WCF does not protect the message on transfer from the client to the service. Any malicious party can read the content of the message, or even alter it.

Signed

When configured for this protection level, WCF ensures that the message could have come only from an authenticated sender and that the message integrity was not compromised during transfer. To accomplish this, WCF appends an encrypted checksum to the message. Upon receiving the message, the service calculates the checksum and compares it to the original. If the two do not match, the message is rejected. As a result, the message is impervious to tampering. However, the message content is still visible during the transfer.

Encrypted and Signed

When configured for this protection level, WCF both signs the message and encrypts its content. The Encrypted and Signed protection level provides integrity, privacy, and authenticity.

The Signed protection level offers a clear trade-off between a measured degree of security and performance. However, I consider this to be a trade-off to avoid, and I recommend that you always opt instead for the Encrypted and Signed protection level. WCF represents the protection level with the ProtectionLevel enum, defined as:

public enum ProtectionLevel
{
   None,
   Sign,
   EncryptAndSign
}

Not all Internet bindings default to the same protection level. Both the NetTcpBinding and the NetNamedPipeBinding default to Encrypted and Signed, yet the NetMsmqBinding defaults to Signed.

NetTcpBinding configuration

NetTcpBinding takes a construction parameter indicating the desired transfer security mode:

public class NetTcpBinding : ...
{
   public NetTcpBinding(SecurityMode securityMode);
   public NetTcpSecurity Security
   {get;}
   //More members
}

The Security property of the type NetTcpSecurity contains the transfer mode (Transport or Message) and two respective properties with their specific settings:

public sealed class NetTcpSecurity
{
   public SecurityMode Mode
   {get;set;}
   public MessageSecurityOverTcp Message
   {get;}
   public TcpTransportSecurity Transport
   {get;}
}

In the intranet security scenario, you should select Transport security for the transfer security mode and set the values of the Transport property of the type TcpTransportSecurity:

public sealed class TcpTransportSecurity
{
   public TcpClientCredentialType ClientCredentialType
   {get;set;}

   public ProtectionLevel ProtectionLevel
   {get;set;}
}

The Transfer property should be initialized with the client credential type set to Windows using the TcpClientCredentialType enum, defined as:

public enum TcpClientCredentialType
{
   None,
   Windows,
   Certificate
}

The Transfer property should also have the protection level set to ProtectionLevel.EncryptAndSign. Since both of those settings are the defaults for this binding, these two declarations are equivalent:

NetTcpBinding binding1 = new NetTcpBinding(  );

NetTcpBinding binding2 = new NetTcpBinding(SecurityMode.Transport);
binding2.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
binding2.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;

Alternatively, you can configure the binding using a config file:

<bindings>
   <netTcpBinding>
      <binding name = "TCPWindowsSecurity">
         <security mode = "Transport">
            <transport
               clientCredentialType = "Windows"
               protectionLevel = "EncryptAndSign"
            />
         </security>
      </binding>
   </netTcpBinding>
</bindings>
Tip
The NetTcpContextBinding and the WSHttpContextBinding also offer the ContextProtectionLevel property of the type ProtectionLevel, used to indicate the desired protection level for the custom context. ContextProtectionLevel defaults to ProtectionLevel.Sign. When using Transport security, the value of ContextProtectionLevel is ignored (since the transport protects the whole message during transfer). In the case of Message security, due to a bug in WCF, only if the service demands higher protection than what the client uses will WCF abort the call. The same is true for the NetNamedPipeContextBinding defined in Appendix B, Headers and Contexts (since it uses the same WCF facility).

For the sake of completeness, although it's not required by the intranet scenario, here is how to configure NetTcpBinding for Message security with username client credentials:

public enum MessageCredentialType
{
   None,
   Windows,
   UserName,
   Certificate,
   IssuedToken
}
public sealed class MessageSecurityOverTcp
{
   public MessageCredentialType ClientCredentialType
   {get;set;}
   //More members
}
NetTcpBinding binding = new NetTcpBinding(SecurityMode.Message);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

NetTcpSecurity offers the Message property of the type MessageSecurityOverTcp. You'll need to set the credentials type using the MessageCredentialType enum. Most bindings use the MessageCredentialType enum for representing Message security client credentials.

Figure 10.1, "NetTcpBinding and security" shows the security-related elements of the NetTcpBinding.

Figure 10.1. NetTcpBinding and security

NetTcpBinding and security

NetTcpBinding has a reference to NetTcpSecurity, which uses the SecurityMode enum to indicate the transfer security mode. When Transport security is used, NetTcpSecurity will use an instance of TcpTransportSecurity containing the client credentials type via the TcpClientCredentialType enum and the configured protection level via the ProtectionLevel enum. When Message security is used, NetTcpSecurity will use an instance of MessageSecurityOverTcp containing the client credentials type via the MessageCredentialType enum.

NetNamedPipeBinding configuration

NetNamedPipeBinding takes a construction parameter indicating the desired transfer security mode:

public class NetNamedPipeBinding : Binding,...
{
   public NetNamedPipeBinding(NetNamedPipeSecurityMode securityMode);

   public NetNamedPipeSecurity Security
   {get;}
   //More members
}

The Security property of the type NetNamedPipeSecurity contains the transfer mode (Transport or None) and a single property with the specific Transport settings:

public sealed class NetNamedPipeSecurity
{
   public NetNamedPipeSecurityMode Mode
   {get;set;}
   public NamedPipeTransportSecurity Transport
   {get;}
}

For the intranet security scenario, select Transport security for the transfer security mode and set the values of the Transport property of the type NamedPipeTransportSecurity:

public sealed class NamedPipeTransportSecurity
{
   public ProtectionLevel ProtectionLevel
   {get;set;}
}

The Transfer property should be initialized with the protection level set to ProtectionLevel.EncryptAndSign. Because this is the default for the binding, these two declarations are equivalent:

NetNamedPipeBinding binding1 = new NetNamedPipeBinding(  );

NetNamedPipeBinding binding2 = new NetNamedPipeBinding(
                                              NetNamedPipeSecurityMode.Transport);
binding2.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;

You can also configure the binding administratively, using a config file:

<bindings>
   <netNamedPipeBinding>
      <binding name = "IPCWindowsSecurity">
         <security mode = "Transport">
            <transport protectionLevel = "EncryptAndSign"/>
         </security>
      </binding>
   </netNamedPipeBinding>
</bindings>

There is no need (or option) to set the client credentials type, since only Windows credentials are supported (see Table 10.2, "Bindings and Transport security client credentials"). Figure 10.2, "NetNamedPipeBinding and security" shows the security-related elements of the NetNamedPipeBinding.

Figure 10.2. NetNamedPipeBinding and security

NetNamedPipeBinding and security

NetNamedPipeBinding has a reference to NetNamedPipeSecurity, which uses the NetNamedPipeSecurityMode enum to indicate the transfer security mode. When Transport security is used, NetTcpSecurity will use an instance of NamedPipeTransportSecurity containing the configured protection level via the ProtectionLevel enum.

NetMsmqBinding configuration

NetMsmqBinding offers a construction parameter for the transfer security mode and a Security property:

public class NetMsmqBinding : MsmqBindingBase
{
   public NetMsmqBinding(NetMsmqSecurityMode securityMode);
   public NetMsmqSecurity Security
   {get;}
   //More members
}

The Security property of the type NetMsmqSecurity contains the transfer mode (Transport or Message) and two respective properties with their specific settings:

public sealed class NetMsmqSecurity
{
   public NetMsmqSecurityMode Mode
   {get;set;}
   public MsmqTransportSecurity Transport
   {get;}
   public MessageSecurityOverMsmq Message
   {get;}
}

For the intranet security scenario, select Transport security for the transfer security mode and set the values of the Transport property of the type MsmqTransportSecurity:

public sealed class MsmqTransportSecurity
{
   public MsmqAuthenticationMode MsmqAuthenticationMode
   {get;set;}
   public ProtectionLevel MsmqProtectionLevel
   {get;set;}
   //More members
}

The Transfer property should be initialized with the client credential type set to Windows domain using the MsmqAuthenticationMode enum, defined as:

public enum MsmqAuthenticationMode
{
   None,
   WindowsDomain,
   Certificate
}

Windows domain is the default credentials type. In addition, you need to set the protection level to ProtectionLevel.EncryptAndSign because the MSMQ binding defaults to ProtectionLevel.Signed. The following two definitions are equivalent:

NetMsmqBinding binding1 = new NetMsmqBinding(  );
binding1.Security.Transport.MsmqProtectionLevel = ProtectionLevel.EncryptAndSign;

NetMsmqBinding binding2 = new NetMsmqBinding(  );
binding2.Security.Mode = NetMsmqSecurityMode.Transport;
binding2.Security.Transport.MsmqAuthenticationMode =
                                            MsmqAuthenticationMode.WindowsDomain;
binding2.Security.Transport.MsmqProtectionLevel = ProtectionLevel.EncryptAndSign;

Alternatively, you can configure the binding using a config file:

<bindings>
   <netMsmqBinding>
      <binding name = "MSMQWindowsSecurity">
         <security mode = "Transport">
            <transport
               msmqAuthenticationMode = "WindowsDomain"
               msmqProtectionLevel = "EncryptAndSign"
            />
         </security>
      </binding>
   </netMsmqBinding>
</bindings>

Figure 10.3, "NetMsmqBinding and security" shows the security-related elements of the NetMsmqBinding.

Figure 10.3. NetMsmqBinding and security

NetMsmqBinding and security

NetMsmqBinding has a reference to NetMsmqSecurity, which uses the NetMsmqSecurityMode enum to indicate the transfer security mode. When Transport security is used, NetMsmqSecurity will use an instance of MsmqTransportSecurity containing the client credentials type via the MsmqAuthenticationMode enum, and the configured protection level via the ProtectionLevel enum. There are similar references to types controlling Message security.

Constraining Message Protection

While a service should ideally use the highest possible level of security, it is actually at the mercy of its host, because the host is the one configuring the binding. This is especially problematic if the service is to be deployed in an unknown environment with an arbitrary host. To compensate, WCF lets service developers insist on a protection level, or rather, constrain the minimum protection level at which their service is willing to operate. Both the service and the client can constrain the protection level, independently of each other. You can constrain the protection level in three places. When constrained at the service contract, all operations on the contract are considered sensitive and protected. When constrained at the operation contract, only that operation is protected; other operations on the same contract are not. Finally, you can constrain the protection level for an individual fault contract. This can be required because sometimes the error information returned to the client is sensitive, containing parameter values, exception messages, and the call stack. The respective contract attributes offer the ProtectionLevel property of the enum type ProtectionLevel:

[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,
                Inherited = false)]
public sealed class ServiceContractAttribute : Attribute
{
   public ProtectionLevel ProtectionLevel
   {get;set;}
   //More members
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
   public ProtectionLevel ProtectionLevel
   {get;set;}
   //More members
}
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true,
                Inherited = false)]
public sealed class FaultContractAttribute : Attribute
{
   public ProtectionLevel ProtectionLevel
   {get;set;}
   //More members
}

As an example, here is how to set the protection level on a service contract:

[ServiceContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
interface IMyContract
{...}

Setting the ProtectionLevel property on the contract attributes merely indicates the low-water mark; that is, the minimum protection level accepted by this contract. If the binding is configured for a lower protection level, it will result in an InvalidOperationException at the service load time or the time the proxy is opened. If the binding is configured for a higher level, the contract will use that level. The ProtectionLevel property on the contract attributes defaults to ProtectionLevel.None, meaning it has no effect.

The desired protection constraint is considered a local implementation detail of the service, so the required protection level is not exported with the service metadata. Consequently, the client may require a different level and enforce it separately from the service.

Tip
Even though the Internet bindings do not offer a protection level property, the protection level constraint at the service-, operation-, or fault-contract level is satisfied when using Transport or Message security. The constraint is not satisfied when security is turned off by using the None security mode.

Authentication

By default, when a client calls a proxy that targets an endpoint whose binding is configured for using Windows credentials with Transport security, there is nothing explicit the client needs to do to pass its credentials. WCF will automatically pass the Windows identity of the client's process to the service:

class MyContractClient : ClientBase<IMyContract>,IMyContract
{...}

MyContractClient proxy = new MyContractClient(  );
proxy.MyMethod(  ); //Client identity passed here
proxy.Close(  );

When the service receives the call, WCF will authenticate the caller on the service side. If the client's credentials represent a valid Windows account, the caller will be allowed to access the requested operation on the service.

Providing alternative Windows credentials

Instead of using the identity of the process in which it happens to be running, the client can pass alternative Windows credentials. The ClientBase<T> base class offers the ClientCredentials property of the type ClientCredentials:

public abstract class ClientBase<T> : ...
{
   public ClientCredentials ClientCredentials
   {get;}
}
public class ClientCredentials : ...,IEndpointBehavior
{
   public WindowsClientCredential Windows
   {get;}
   //More members
}

ClientCredentials contains the property Windows of the type WindowsClientCredential, defined as:

public sealed class WindowsClientCredential
{
   public NetworkCredential ClientCredential
   {get;set;}
   //More members
}

WindowsClientCredential has the property ClientCredential of the type NetworkCredential, which is where the client needs to set the alternative credentials:

public class NetworkCredential : ...
{
   public NetworkCredential(  );
   public NetworkCredential(string userName,string password);
   public NetworkCredential(string userName,string password,string domain);

   public string Domain
   {get;set;}
   public string UserName
   {get;set;}
   public string Password
   {get;set;}
}

Example 10.3, "Providing alternative Windows credentials" demonstrates how to use these classes and properties to provide alternative Windows credentials.

Example 10.3. Providing alternative Windows credentials

MyContractClient proxy = new MyContractClient(  );

proxy.ClientCredentials.Windows.ClientCredential.Domain   = "MyDomain";
proxy.ClientCredentials.Windows.ClientCredential.UserName = "MyUsername";
proxy.ClientCredentials.Windows.ClientCredential.Password = "MyPassword";

proxy.MyMethod(  );
proxy.Close(  );

Once you specify an alternative identity and open the proxy, the proxy cannot use any other identity later.

Warning
If you do try specifying alternative credentials after opening the proxy, those credentials will be silently ignored.

Clients can use the technique demonstrated in Example 10.3, "Providing alternative Windows credentials" when the credentials provided are collected dynamically at runtime, perhaps using a login dialog box.

When working with a channel factory instead of a proxy class, the ChannelFactory base class offers the Credentials property of the type ClientCredentials:

public abstract class ChannelFactory : ...
{
   public ClientCredentials Credentials
   {get;}
   //More members
}
public class ChannelFactory<T> : ChannelFactory,...
{
   public T CreateChannel(  );
   //More members
}

In this case, simply set the alternative credentials in the Credentials property, as was done in Example 10.3, "Providing alternative Windows credentials":

ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>(...);

factory.Credentials.Windows.ClientCredential.Domain   = "MyDomain";
factory.Credentials.Windows.ClientCredential.UserName = "MyUsername";
factory.Credentials.Windows.ClientCredential.Password = "MyPassword";

IMyContract proxy = factory.CreateChannel(  );

Note that you cannot use the static CreateChannel( ) methods of ChannelFactory<T>, since you have to first instantiate a factory in order to access the Credentials property.

Identities

All Windows processes run with an authenticated security identity, and the process hosting a WCF service is no different. The identity is actually a Windows account whose security token is attached to the process (and to every thread in the process). However, it is up to the application administrator to decide which identity to use. One option is to have the host run with an interactive user identity; that is, the identity of the user who launched the host process. An interactive identity is typically used when self-hosting and is ideal for debugging, because the debugger will automatically attach itself to the host process when launched from within Visual Studio. However, relying on an interactive identity is impractical for deployment on a server machine, where there will not necessarily be a logged-on user, and if there is a logged-on user that user may not have the necessary credentials to perform the requested work. For production deployment, you typically rely on a designated account, which is a preset Windows account used primarily by your service or services. To launch the service under a designated account, you can use the "Run as" shell option. However, "Run as" is useful only for simple testing. You can also have an NT service as your host and use the Control Panel Services applet to assign a designated identity to the host. If you're hosting in IIS 5/6 or the WAS, you can use those environments' configuration tools to assign a designated identity to the process from the pool.

The IIdentity interface

In .NET, the IIdentity interface (from the System.Security.Principal namespace) represents a security identity:

public interface IIdentity
{
   string AuthenticationType
   {get;}
   bool IsAuthenticated
   {get;}
   string Name
   {get;}
}

The interface lets you know whether the identity behind the interface is authenticated (and, if so, which authentication mechanism was used) and allows you to obtain the name of the identity. Out of the box, WCF takes advantage of three implementations of IIdentity offered by .NET: WindowsIdentity, GenericIdentity, and X509Identity. The WindowsIdentity class represents a Windows account. The GenericIdentity class is a general-purpose class whose main use is to wrap an identity name with an IIdentity. With both GenericIdentity and WindowsIdentity, if the identity name is an empty string, that identity is considered unauthenticated, and any other non-zero-length name is considered authenticated. Finally, X509Identity is an internal class that represents an identity that was authenticated using an X509 certificate. The identity behind an X509Identity is always authenticated.

Working with WindowsIdentity

The WindowsIdentity class offers a few useful methods above and beyond the mere implementation of IIdentity:

public class WindowsIdentity : IIdentity,...
{
   public WindowsIdentity(string sUserPrincipalName);
   public static WindowsIdentity GetAnonymous(  );
   public static WindowsIdentity GetCurrent(  );
   public virtual bool IsAnonymous
   {get;}
   public virtual bool IsAuthenticated
   {get;}
   public virtual string Name
   {get;}
   //More members
}

The IsAnonymous Boolean property indicates whether the underlying identity is anonymous and the GetAnonymous( ) method returns an anonymous Windows identity, typically used for impersonation to mask the real identity:

WindowsIdentity identity = WindowsIdentity.GetAnonymous(  );
Debug.Assert(identity.Name == ");
Debug.Assert(identity.IsAuthenticated == false);
Debug.Assert(identity.IsAnonymous == true);

The GetCurrent( ) static method returns the identity of the process where it is called. That identity is always non-anonymous and authenticated:

WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(  );
Debug.Assert(currentIdentity.Name != ");
Debug.Assert(currentIdentity.IsAuthenticated == true);
Debug.Assert(currentIdentity.IsAnonymous == false);

The Security Call Context

Every operation on a secured WCF service has a security call context. The security call context is represented by the class ServiceSecurityContext, defined as:

public class ServiceSecurityContext
{
   public static ServiceSecurityContext Current
   {get;}
   public bool IsAnonymous
   {get;}
   public IIdentity PrimaryIdentity
   {get;}
   public WindowsIdentity WindowsIdentity
   {get;}
   //More members
}

The main use for the security call context is for custom security mechanisms, as well as analysis and auditing. While it is presented here in the context of the intranet scenario, all other secured scenarios have use for the security call context as well.

Note that in spite of its name, this is the security context of the call, not the service. Two operations on the same service can definitely have different security call contexts.

The security call context is stored in the TLS, so every method on every object down the call chain from the service can access the security call context, including your service constructor. To obtain your current security call context, simply access the Current static property. Another way of accessing the security call context is via the ServiceSecurityContext property of the OperationContext:

public sealed class OperationContext : ...
{
   public ServiceSecurityContext ServiceSecurityContext
   {get;}
   //More members
}

Regardless of which mechanism you use, you will get the same object:

ServiceSecurityContext context1 = ServiceSecurityContext.Current;
ServiceSecurityContext context2 = OperationContext.Current.ServiceSecurityContext;
Debug.Assert(context1 == context2);
Warning
Your service has a security call context only if security is enabled. When security is disabled, ServiceSecurityContext.Current returns null.

The PrimaryIdentity property of ServiceSecurityContext contains the identity of the immediate client up the call chain. If the client is unauthenticated, PrimaryIdentity will reference an implementation of IIdentity with a blank identity. When Windows authentication is used, the PrimaryIdentity property will be set to an instance of WindowsIdentity.

The WindowsIdentity property is meaningful only when using Windows authentication, and it will always be of the type WindowsIdentity. When valid Windows credentials are provided, the WindowsIdentity property will contain the corresponding client identity and will match the value of PrimaryIdentity.

Tip
The constructor of a singleton service does not have a security call context, since it is called when the host is launched, not as a result of a client call.

Impersonation

Some resources, such as the filesystem, SQL Server, sockets, and even DCOM objects, grant access to themselves based on the caller's security token. Typically, the host process is assigned an identity with elevated permissions that are required to access such resources, so that it can function properly. Clients, however, typically have restricted credentials compared with those of the service. Legacy technologies such as unmanaged Visual Basic or C++ did not offer role-based security support, so developers used impersonation to address this credentials gap. Impersonation lets the service assume the client's identity, primarily in order to verify whether the client is authorized to perform the work it's asking the service to do. Impersonation has a number of key detrimental effects on your application, which will be discussed at the end of this section. Instead of impersonation, you should apply role-based security to authorize the callers, coupled with a trusted subsystem pattern across layers. That said, many developers are used to designing systems using impersonation, so both .NET and WCF support this technique.

Manual impersonation

The service can impersonate its calling client by calling the Impersonate( ) method of the WindowsIdentity class:

public class WindowsIdentity : IIdentity,...
{
   public virtual WindowsImpersonationContext Impersonate(  );
   //More members
}
public class WindowsImpersonationContext : IDisposable
{
   public void Dispose(  );
   public void Undo(  );
}

Impersonate( ) returns an instance of WindowsImpersonationContext containing the service's previous identity. To revert back to that identity, the service calls the Undo( ) method. To impersonate a client, the service needs to call Impersonate( ) on the identity of the caller, which is available via the WindowsIdentity property of its security call context, as shown in Example 10.4, "Explicit impersonation and reversion".

Example 10.4. Explicit impersonation and reversion

class MyService : IMyContract
{
   public void MyMethod(  )
   {
      WindowsImpersonationContext impersonationContext =
                      ServiceSecurityContext.Current.WindowsIdentity.
Impersonate(  );
      try
      {
         /*  Do work as client */
      }
      finally
      {
         impersonationContext.Undo(  );
      }
   }
}

Note in Example 10.4, "Explicit impersonation and reversion" that the call to Undo( ) is in the finally statement, so the service will revert to its old identity even if exceptions occur. To somewhat simplify reverting, the WindowsImpersonationContext implementation of Dispose( ) also reverts, which enables you to use it in a using statement:

public void MyMethod(  )
{
   using(ServiceSecurityContext.Current.WindowsIdentity.Impersonate(  ))
   {
      /*  Do work as client */
   }
}

Declarative impersonation

Instead of impersonating manually, you can instruct WCF to automatically impersonate the caller of the method. The OperationBehavior attribute offers the Impersonation property of the enum type ImpersonationOption:

public enum ImpersonationOption
{
   NotAllowed,
   Allowed,
   Required
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationBehaviorAttribute : Attribute,IOperationBehavior
{
   public ImpersonationOption Impersonation
   {get;set;}
   //More members
}

The default value is ImpersonationOption.NotAllowed. This value indicates that WCF should not auto-impersonate, but you can write code (as in Example 10.4, "Explicit impersonation and reversion") that explicitly impersonates.

ImpersonationOption.Allowed instructs WCF to automatically impersonate the caller whenever Windows authentication is used, but it has no effect with other authentication mechanisms. When WCF auto-impersonates, it will also auto-revert to the previous service identity once the method returns.

The ImpersonationOption.Required value mandates the use of Windows authentication and will throw an exception if any other authentication mechanism is used. As its name implies, with this setting WCF will always auto-impersonate (and revert) in every call to the operation:

class MyService : IMyContract
{
   [OperationBehavior(Impersonation = ImpersonationOption.Required)]
   public void MyMethod(  )
   {
      /* Do work as client */
   }
}

Note that there is no way to use declarative impersonation with the service constructor because you cannot apply the OperationBehavior attribute on a constructor. Constructors can only use manual impersonation. If you do impersonate in the constructor, always revert as well in the constructor, to avoid side effects on the operations of the service (and even other services in the same host).

Impersonating all operations

In the event that you need to enable impersonation in all the service operations, the ServiceHostBase class has the Authorization property of the type ServiceAuthorizationBehavior:

public abstract class ServiceHostBase : ...
{
   public ServiceAuthorizationBehavior Authorization
   {get;}
   //More members
}
public sealed class ServiceAuthorizationBehavior : IServiceBehavior
{
   public bool ImpersonateCallerForAllOperations
   {get;set;}
   //More members
}

ServiceAuthorizationBehavior provides the Boolean property ImpersonateCallerForAllOperations, which is false by default. Contrary to what its name implies, when set to true, this property merely verifies that the service does not have any operations configured with ImpersonationOption.NotAllowed. This constraint is verified at service load time, yielding an InvalidOperationException when violated.

In effect, when Windows authentication is used, this will amount to the service automatically impersonating the client in all operations, but all the operations must be explicitly decorated with ImpersonationOption.Allowed or ImpersonationOption.Required. ImpersonateCallerForAllOperations has no effect on constructors.

You can set the ImpersonateCallerForAllOperations property programmatically or in the config file. If you set it programmatically, you can do so only before opening the host:

ServiceHost host = new ServiceHost(typeof(MyService));
host.Authorization.ImpersonateCallerForAllOperations = true;
host.Open(  );

If you set it using a config file, you need to reference the matching service behavior in the service declaration:

<services>
   <service name = "MyService" behaviorConfiguration= "ImpersonateAll">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "ImpersonateAll">
         <serviceAuthorization impersonateCallerForAllOperations = "true"/>
      </behavior>
   </serviceBehaviors>
</behaviors>

To automate impersonating in all operations without the need to apply the OperationBehavior attribute on every method, I wrote the SecurityHelper static class, with the ImpersonateAll( ) extension methods:

public static class SecurityHelper
{
   public static void ImpersonateAll(this ServiceHostBase host);
   public static void ImpersonateAll(this ServiceDescription description);
   //More members
}

The extension methods work on both ServiceHost and ServiceHost<T>.

You can only call ImpersonateAll( ) before opening the host:

//Will impersonate in all operations
class MyService : IMyContract
{
   public void MyMethod(  )
   {...}
}
ServiceHost host = new ServiceHost(typeof(MyService));
host.ImpersonateAll(  );
host.Open(  );

Example 10.5, "Implementing SecurityHelper.ImpersonateAll( )" shows the implementation of ImpersonateAll( ).

Example 10.5. Implementing SecurityHelper.ImpersonateAll( )

public static class SecurityHelper
{
   public static void ImpersonateAll(this ServiceHostBase host)
   {
      host.Authorization.ImpersonateCallerForAllOperations = true;
      host.Description.ImpersonateAll(  );
   }
   public static void ImpersonateAll(this ServiceDescription description)
   {
      foreach(ServiceEndpoint endpoint in description.Endpoints)
      {
         if(endpoint.Contract.Name == "IMetadataExchange")
         {
            continue;
         }
         foreach(OperationDescription operation in endpoint.Contract.Operations)
         {
            OperationBehaviorAttribute attribute = operation.Behaviors.
                                               Find<OperationBehaviorAttribute>(  );
            attribute.Impersonation = ImpersonationOption.Required;
         }
      }
   }
   //More members
}

In Example 10.5, "Implementing SecurityHelper.ImpersonateAll( )", ImpersonateAll( ) (for the sake of good manners) first sets the ImpersonateCallerForAllOperations property of the provided host to true, then obtains the service description from the host and calls the other overloaded extension method of ServiceDescription. This version explicitly configures all operations with ImpersonationOption.Required, by iterating over the endpoints collection of the service description. For each endpoint (except the metadata exchange endpoints), ImpersonateAll( ) accesses the operations collection of the contract. For each operation, there is always exactly one OperationBehaviorAttribute in the collection of operation behaviors, even if you did not provide one explicitly. The method then simply sets the Impersonation property to ImpersonationOption.Required.

Restricting impersonation

Authorization and authentication protect the service from being accessed by unauthorized, unauthenticated, potentially malicious clients. However, how should the client be protected from malicious services? One of the ways an adversarial service could abuse the client is by assuming the client's identity and credentials and causing harm while masquerading as the client. This tactic enables the malicious service both to leave an identity trail pointing back to the client and to elevate its own potentially demoted, less-privileged credentials to the client's level.

In some cases, the client may not want to allow the service to obtain its identity at all. WCF therefore lets the client indicate the degree to which the service can obtain the client's identity and how it can use it. Impersonation is actually a range of options indicating the level of trust between the client and the service. The WindowsClientCredential class provides the AllowedImpersonationLevel enum of the type TokenImpersonationLevel, found in the System.Security.Principal namespace:

public enum TokenImpersonationLevel
{
   None,
   Anonymous,
   Identification,
   Impersonation,
   Delegation
}
public sealed class WindowsClientCredential
{
   public TokenImpersonationLevel AllowedImpersonationLevel
   {get;set;}
   //More members
}

The client can use AllowedImpersonationLevel to restrict the allowed impersonation level both programmatically and administratively. For example, to programmatically restrict the impersonation level to TokenImpersonationLevel.Identification, before opening the proxy the client would write:

MyContractClient proxy = new MyContractClient(  );
proxy.ClientCredentials.Windows.AllowedImpersonationLevel =
                                            TokenImpersonationLevel.Identification;
proxy.MyMethod(  );
proxy.Close(  );

When using a config file, the administrator should define the allowed impersonation level as a custom endpoint behavior and reference it from the relevant endpoint section:

<client>
   <endpoint behaviorConfiguration = "ImpersonationBehavior"
      ...
   />
</client>
<behaviors>
   <endpointBehaviors>
      <behavior name = "ImpersonationBehavior">
         <clientCredentials>
            <windows allowedImpersonationLevel = "Identification"/>
         </clientCredentials>
      </behavior>
   </endpointBehaviors>
</behaviors>

TokenImpersonationLevel.None simply means that no impersonation level is assigned, so the client provides no identity information. This setting therefore amounts to the same thing as TokenImpersonationLevel.Anonymous, where the client provides no credentials at all. These two values are, of course, the safest from the client's perspective, but they are the least useful options from the application's perspective, since the service cannot perform any authentication or authorization. Not sharing credentials is possible only if the service is configured for anonymous access or for having no security, which is not the case with the intranet scenario. If the service is configured for Windows security, these two values yield an ArgumentOutOfRangeException on the client side.

With TokenImpersonationLevel.Identification, the service can identify the client (i.e., obtain the security identity of the calling client). The service, however, is not allowed to impersonate the client-everything the service does must be done under the service's own identity. Trying to impersonate will throw an ArgumentOutOfRangeException on the service side. Note, however, that if the service and the client are on the same machine, the service will still be able to impersonate the client, even when TokenImpersonationLevel.Identification is used. TokenImpersonationLevel.Identification is the default value used with Windows security and is the recommended value for the intranet scenario.

TokenImpersonationLevel.Impersonation grants the service permission both to obtain the client's identity and to impersonate the client. Impersonation indicates a great deal of trust between the client and the service, since the service can do anything the client can do, even if the service host is configured to use a less privileged identity. The only difference between the real client and the impersonating service is that if the service is on a separate machine from the client, it cannot access resources or objects on other machines as the client, because the service machine does not really have the client's password. In the case where the service and the client are on the same machine, the service impersonating the client can make one network hop to another machine, since the machine it resides on can still authenticate the impersonated client identity.

Finally, TokenImpersonationLevel.Delegation provides the service with the client's Kerberos ticket. In this case, the service can freely access resources on any machine as the client. If service is also configured for delegation, when it calls other downstream services the client's identity could be propagated further and further down the call chain. Delegation-required Kerberos authentication is not possible on Windows workgroup installations. Both the client and server user accounts must be properly configured in Active Directory to support delegation, due to the enormous trust (and hence security risk) involved. Delegation uses by default another security service called cloaking, which propagates the caller's identity along the call chain.

Delegation is extremely dangerous from the client's perspective, since the client has no control over who ends up using its identity, or where. When the impersonation level is set to TokenImpersonationLevel.Impersonation, the client takes a calculated risk: it knows which services it is accessing, and if those services are on a different machine, the client identity cannot propagate across the network. I consider delegation something that enables the service not just to impersonate the client, but to act as an imposter; security-wise, as far as the client is concerned, this is tantamount to waiving security.

Avoiding impersonation

You should design your services so that they do not rely on impersonation, and your clients should use TokenImpersonationLevel.Identification. Impersonation is a relic of the '90s, typically used in classic two-tier systems in the absence of role-based security support, where scalability was not a concern and managing a small number of identities across resources was doable.

As a general design guideline, the further down the call chain from the client, the less relevant the client's identity is. If you use some kind of layered approach in your system design, each layer should run under its own identity, authenticate its immediate callers, and implicitly trust its calling layer to authenticate its callers, thereby maintaining a chain of trusted, authenticated callers. This is called the trusted subsystem pattern. Impersonation, on the other hand, requires you to keep propagating the identity further and further down the call chain, all the way to the underlying resources. Doing so impedes scalability, because many resources (such as SQL Server connections) are allocated per identity. With impersonation, you will need as many resources as clients, and you will not be able to benefit from resource pooling (such as connection pooling). Impersonation also complicates resource administration, because you need to grant access to the resources to all of the original client identities, and there could be numerous such identities to manage. A service that always runs under its own identity poses no such problems, regardless of how many identities access that service. To control access to the resources, you should use authorization, as discussed next.

Multitier systems that do use impersonation typically gravitate toward delegation, since that is the only way to propagate the client identities across tiers and machines. In fact, the main reason developers today use impersonation has little to do with resource access authorization (which can easily be accomplished with role-based security); instead, it is used as a mechanism for auditing and identity propagation. If the application is required to provide at lower layers the identity of the topmost client or all clients up the chain, impersonation (if not full-fledged delegation) may look like a viable option. There are three good solutions for these requirements. First, if the business use cases require you to provide the top-level identity to downstream parties, there is nothing wrong with providing it as explicit method arguments since they are part of the required behavior of the system. The second solution is to use security audits (discussed later) and leave a trail across the call chain. At any point, you can reconstruct that chain of identities from the local audits. The third option is to propagate the identity of the original caller (or the entire stack of callers) in the message headers. Doing that transparently across the call chain requires passing the identities out-of-band in the headers and using the elegant generic interception technique described in Appendix E, Generic Interceptor. ServiceModelEx contains those helper classes (look for SecurityCallStackClientBase<T>, OperationSecurityCallStackAttribute, and SecurityCallStackBehaviorAttribute).

Finally, relying on impersonation precludes non-Windows authentication mechanisms. If you do decide to use impersonation, use it judiciously and only as a last resort, when there is no other, better design approach.

Tip
Impersonation is not possible with queued services.

Authorization

While authentication deals with verifying that the client is indeed who the client claims to be, most applications also need to verify that the client (or more precisely, the identity it presents) has permission to perform the operation. Since it would be impractical to program access permissions for each individual identity, it is better to grant permissions to the roles clients play in the application domain. A role is a symbolic category of identities that share the same security privileges. When you assign a role to an application resource, you are granting access to that resource to anyone who is a member of that role. Discovering the roles clients play in your business domain is part of your application-requirements analysis and design, just like factoring services and interfaces. By interacting with roles instead of particular identities, you isolate your application from changes made in real life, such as adding new users, moving existing users between positions, promoting users, or users leaving their jobs. .NET allows you to apply role-based security both declaratively and programmatically, if the need to verify role membership is based on a dynamic decision.

The security principal

For security purposes, it is convenient to lump together an identity and the information about its role membership. This representation is called the security principal.

The principal in .NET is any object that implements the IPrincipal interface, defined in the System.Security.Principal namespace:

public interface IPrincipal
{
   IIdentity Identity
   {get;}
   bool IsInRole(string role);
}

The IsInRole( ) method simply returns true if the identity associated with this principal is a member of the specified role, and false otherwise. The Identity read-only property provides access to read-only information about the identity, in the form of an object implementing the IIdentity interface. Out of the box, .NET offers several implementations of IPrincipal. GenericPrincipal is a general-purpose principal that has to be preconfigured with the role information. It is typically used when no authorization is required, in which case GenericPrincipal wraps a blank identity. The WindowsPrincipal class looks up role membership information inside the Windows NT groups.

Every .NET thread has a principal object associated with it, obtained via the CurrentPrincipal static property of the Thread class:

public sealed class Thread
{
   public static IPrincipal CurrentPrincipal
   {get;set;}
   //More members
}

For example, here is how to discover the username as well as whether or not the caller was authenticated:

IPrincipal principal = Thread.CurrentPrincipal;
string userName = principal.Identity.Name;
bool isAuthenticated = principal.Identity.IsAuthenticated;

Selecting an authorization mode

As presented earlier, the ServiceHostBase class provides the Authorization property of the type ServiceAuthorizationBehavior. ServiceAuthorizationBehavior has the PrincipalPermissionMode property of the enum type PrincipalPermissionMode, defined as:

public enum PrincipalPermissionMode
{
   None,
   UseWindowsGroups,
   UseAspNetRoles,
   Custom
}
public sealed class ServiceAuthorizationBehavior : IServiceBehavior
{
   public PrincipalPermissionMode PrincipalPermissionMode
   {get;set;}
   //More members
}

Before opening the host, you can use the PrincipalPermissionMode property to select the principal mode; that is, which type of principal to install to authorize the caller.

If PrincipalPermissionMode is set to PrincipalPermissionMode.None, principal-based authorization is impossible. After authenticating the caller (if authentication is required at all), WCF installs GenericPrincipal with a blank identity and attaches it to the thread that invokes the service operation. That principal will be available via Thread.CurrentPrincipal.

When PrincipalPermissionMode is set to PrincipalPermissionMode.UseWindowsGroups, WCF installs a WindowsPrincipal with an identity matching the provided credentials. If no Windows authentication took place (because the service did not require it), WCF will install a WindowsPrincipal with a blank identity.

PrincipalPermissionMode.UseWindowsGroups is the default value of the PrincipalPermissionMode property, so these two definitions are equivalent:

ServiceHost host1 = new ServiceHost(typeof(MyService));

ServiceHost host2 = new ServiceHost(typeof(MyService));
host2.Authorization.PrincipalPermissionMode =
                                          PrincipalPermissionMode.UseWindowsGroups;

When using a config file, you need to reference a custom behavior section assigning the principal mode:

<services>
   <service name = "MyService" behaviorConfiguration = "WindowsGroups">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "WindowsGroups">
         <serviceAuthorization principalPermissionMode = "UseWindowsGroups"/>
      </behavior>
   </serviceBehaviors>
</behaviors>

Declarative role-based security

You apply service-side declarative role-based security using the attribute PrincipalPermissionAttribute, defined in the System.Security.Permissions namespace:

public enum SecurityAction
{
   Demand,
   //More members
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class PrincipalPermissionAttribute : CodeAccessSecurityAttribute
{
   public PrincipalPermissionAttribute(SecurityAction action);

   public bool Authenticated
   {get;set; }
   public string Name
   {get;set;}
   public string Role
   {get;set;}
   //More members
}

The PrincipalPermission attribute lets you declare the required role membership. For the intranet scenario, when you specify a Windows NT group as a role, you don't have to prefix the role name with your domain or machine name (if you wish to authorize against its roles). You can also explicitly specify another domain, if you have a trust relationship with it.

In Example 10.6, "Declarative role-based security on the intranet", the declaration of the PrincipalPermission attribute grants access to MyMethod( ) only to callers whose identities belong to the Managers group.

Example 10.6. Declarative role-based security on the intranet

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod(  );
}
class MyService : IMyContract
{
   [PrincipalPermission(SecurityAction.Demand,Role = "Manager")]
   public void MyMethod(  )
   {...}
}

If the caller is not a member of that role, .NET throws an exception of type SecurityException.

Tip
When experimenting with Windows role-based security, you often add users to or remove users from user groups. Because Windows caches user-group information at login time, the changes you make are not reflected until the next login.

If multiple roles are allowed to access the method, you can apply the attribute multiple times:

[PrincipalPermission(SecurityAction.Demand,Role = "Manager")]
[PrincipalPermission(SecurityAction.Demand,Role = "Customer")]
public void MyMethod(  )
{...}

When multiple PrincipalPermission attributes are used, .NET verifies that the caller is a member of at least one of the demanded roles. If you want to verify that the caller is a member of both roles, you need to use programmatic role membership checks, discussed later.

While the PrincipalPermission attribute by its very definition can be applied on methods and classes, in a WCF service class you can apply it only on methods. The reason is that in WCF, unlike with normal classes, the service class constructor always executes under a GenericPrincipal with a blank identity, regardless of the authentication mechanisms used. As a result, the identity under which the constructor is running is unauthenticated and will always fail any kind of authorization attempt (even if the client is a member of the role and even when not using Windows NT groups):

//Will always fail
[PrincipalPermission(SecurityAction.Demand,Role = "...")]
class MyService : IMyContract
{...}
Warning
Avoid sensitive work that requires authorization in the service constructor. With a per-call service, perform such work in the operations themselves, and with a sessionful service, provide a dedicated Initialize( ) operation where you can initialize the instance and authorize the callers.

By setting the Name property of the PrincipalPermission attribute, you can even insist on granting access only to a particular user:

[PrincipalPermission(SecurityAction.Demand,Name = "John")]

or to a particular user that is a member of a particular role:

[PrincipalPermission(SecurityAction.Demand,Name = "John",
                     Role = "Manager")]

These practices are inadvisable, however, because it is best to avoid hardcoding usernames.

Tip
Declarative role-based security hardcodes the role name. If your application looks up role names dynamically you have to use programmatic role verification, as presented next.

Programmatic role-based security

Sometimes you need to programmatically verify role membership. Usually, you need to do that when the decision as to whether to grant access depends both on role membership and on some other values known only at call time, such as parameter values, time of day, and location. Another case in which programmatic role membership verification is needed is when you're dealing with localized user groups. To demonstrate the first category, imagine a banking service that lets clients transfer sums of money between two specified accounts. Only customers and tellers are allowed to call the TransferMoney( ) operation, with the following business rule: if the amount transferred is greater than 50,000, only tellers are allowed to do the transfer. Declarative role-based security can verify that the caller is either a teller or a customer, but it cannot enforce the additional business rule. For that, you need to use the IsInRole( ) method of IPrincipal, as shown in Example 10.7, "Programmatic role-based security".

Example 10.7. Programmatic role-based security

[ServiceContract]
interface IBankAccounts
{
   [OperationContract]
   void TransferMoney(double sum,long sourceAccount,long destinationAccount);
}
static class AppRoles
{
   public const string Customer = "Customer";
   public const string Teller   = "Teller";
}
class BankService : IBankAccounts
{

   [PrincipalPermission(SecurityAction.Demand,Role = AppRoles.Customer)]
   [PrincipalPermission(SecurityAction.Demand,Role = AppRoles.Teller)]
   public void TransferMoney(double sum,long sourceAccount,long destinationAccount)
   {
      IPrincipal  principal = Thread.CurrentPrincipal;
      Debug.Assert(principal.Identity.IsAuthenticated);

      bool isCustomer = principal.IsInRole(AppRoles.Customer);
      bool isTeller   = principal.IsInRole(AppRoles.Teller);

      if(isCustomer & ! isTeller)
      {
         if(sum > 50000)
         {
            string message = "Caller does not have sufficient authority to" +
                             "transfer this sum";
            throw new SecurityException(message);
         }
      }
      DoTransfer(sum,sourceAccount,destinationAccount);
   }
   //Helper method
   void DoTransfer(double sum,long sourceAccount,long destinationAccount)
   {...}
}

Example 10.7, "Programmatic role-based security" also demonstrates a number of other points. First, even though it uses programmatic role membership verification with the value of the sum argument, it still uses declarative role-based security as the first line of defense, allowing access only to clients who are members of the Customer or Teller roles. Second, you can programmatically assert that the caller is authenticated using the IsAuthenticated property of IIdentity. Finally, note the use of the AppRoles static class to encapsulate the actual string used for the role to avoid hardcoding the roles in multiple places.

Tip
There is a complete disconnect between role-based security and the actual principal type. When the PrincipalPermission attribute is asked to verify role membership, it simply gets hold of its thread's current principal in the form of IPrincipal, and calls its IsInRole( ) method. This is also true of programmatic role membership verification that uses only IPrincipal, as shown in Example 10.7, "Programmatic role-based security". The separation of the IPrincipal interface from its implementation is the key to providing other role-based security mechanisms besides Windows NT groups, as you will see in the other scenarios.

Identity Management

In the intranet scenario, after successful authentication, WCF will attach to the operation thread a principal identity of the type WindowsIdentity, which will have the value of its Name property set to the username (or Windows account) provided by the client. Since valid credentials are provided, the security call context's two identities-the primary identity and the Windows identity-will be set to the same identity as the principal identity. All three identities will be considered authenticated. The identities and their values are shown in Table 10.4, "Identity management in the intranet scenario".

Windows Roles Localization

If your application is deployed in international markets and you use Windows groups as roles, it's likely the role names will not match. In the intranet scenario, the principal object attached to the thread accessing the service is of the type WindowsPrincipal:

public class WindowsPrincipal : IPrincipal
{
   public WindowsPrincipal(WindowsIdentity ntIdentity);

   //IPrincipal implementation
   public virtual IIdentity Identity
   {get;}
   public virtual bool IsInRole(string role);

   //Additional methods:
   public virtual bool IsInRole(int rid);
   public virtual bool IsInRole(WindowsBuiltInRole role);
}

WindowsPrincipal provides two additional IsInRole( ) methods that are intended to ease the task of localizing Windows NT groups. You can provide IsInRole( ) with an enum of the type WindowsBuiltInRole matching the built-in NT roles, such as WindowsBuiltInRole.Administrator or WindowsBuiltInRole.User. The other version of IsInRole( ) accepts an integer indexing specific roles. For example, a role index of 512 maps to the Administrators group. The MSDN Library contains a list of both the predefined indexes and ways to provide your own aliases and indexes to user groups.

Table 10.4. Identity management in the intranet scenario

Identity

Type

Value

Authenticated

Thread principal

WindowsIdentity

Username

Yes

Security context primary

WindowsIdentity

Username

Yes

Security context Windows

WindowsIdentity

Username

Yes


Note that while the host processes retain their designated identities, the principal identity will be that of the caller. I call this behavior soft impersonation. When it is used in conjunction with role-based security, it largely negates the need to ever perform real impersonation and replace the security token with that of the client.

Callbacks

When it comes to security on the intranet, there are several key differences between normal service operations and callbacks. First, with a callback contract you can only assign a protection level at the operation level, not the callback contract level. For example, this protection-level constraint will be ignored:

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{...}

//Demand for protection level will be ignored
[ServiceContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
interface IMyContractCallback
{...}

Only the service contract designating the callback contract can set a contract-level protection constraint. WCF deliberately ignores the service contract attribute on the callback contract (as explained in Chapter 5, Operations) to avoid a potential conflict between two contract attributes that apply to the same channel.

You can take advantage of operation-level demand for a protection level as follows:

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{...}

interface IMyContractCallback
{
   [OperationContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
   void OnCallback(  );
}

All calls into the callback object come in with an unauthenticated principal, even if Windows security was used across the board to invoke the service. As a result, the principal identity will be set to a Windows identity with a blank identity, which will preclude authorization and role-based security.

While the callback does have a security call context, the Windows identity will be set to a WindowsIdentity instance with a blank identity, which will preclude impersonation. The only meaningful information will be in the primary identity, which will be set to the service host's process identity and machine name:

class MyClient : IMyContractCallback
{
   public void OnCallback(  )
   {
      IPrincipal principal = Thread.CurrentPrincipal;
      Debug.Assert(principal.Identity.IsAuthenticated == false);

      ServiceSecurityContext context = ServiceSecurityContext.Current;
      Debug.Assert(context.PrimaryIdentity.Name == "MyHost/localhost");

      Debug.Assert(context.IsAnonymous == false);
   }
}

I recommend avoiding any sensitive work in the callback, since you cannot easily use role-based security.

In the Internet scenario, the clients or services may not be using WCF, or even Windows. If you are writing an Internet service or client, you cannot assume the use of WCF on the other end. In addition, an Internet application typically has a relatively large number of clients calling the service. These client calls originate from outside the firewall. You need to rely on HTTP for transport, and multiple intermediaries are possible. In an Internet application, you typically do not want to use Windows accounts and groups for credentials; instead, the application needs to access some custom credentials store. That said, you could still be using Windows security, as demonstrated later.

Securing the Internet Bindings

In an Internet application, you must use Message security for the transfer security mode to provide for end-to-end security across all intermediaries. The client should provide credentials in the form of a username and password, as this is a safe, low common denominator that all platforms support. For the Internet scenario, you should use the WSHttpBinding and the WSDualHttpBinding. You cannot use the basic binding because it does not provide for username credentials over Message security. In addition, if you have an intranet application that uses the NetTcpBinding but you do not wish to use Windows security for user accounts and groups, you should follow the same configuration as with the WS-based bindings. This is done uniformly across these bindings by selecting MessageCredentialType.Username for the client credentials type used with Message security. You need to configure the bindings this way both at the client and at the service.

WSHttpBinding configuration

WSHttpBinding offers the Security property of the type WSHttpSecurity:

public class WSHttpBinding : WSHttpBindingBase
{
   public WSHttpBinding(  );
   public WSHttpBinding(SecurityMode securityMode);
   public WSHttpSecurity Security
   {get;}
   //More members
}

With WSHttpSecurity, you need to set the Mode property of the type SecurityMode to SecurityMode.Message. The Message property of WSHttpSecurity will then take effect:

public sealed class WSHttpSecurity
{
   public SecurityMode Mode
   {get;set;}
   public NonDualMessageSecurityOverHttp Message
   {get;}
   public HttpTransportSecurity Transport
   {get;}
}

Message is of the type NonDualMessageSecurityOverHttp, which derives from MessageSecurityOverHttp:

public class MessageSecurityOverHttp
{
   public MessageCredentialType ClientCredentialType
   {get;set;}
   //More members
}
public sealed class NonDualMessageSecurityOverHttp : MessageSecurityOverHttp
{...}

You need to set the ClientCredentialType property of MessageSecurityOverHttp to MessageCredentialType.Username. Recall that the default Message security credentials type of the WSHttpBinding is Windows (see Table 10.3, "Bindings and Message security client credentials").

Because Message security is the default security mode of the WSHttpBinding (see Table 10.1, "Bindings and transfer security modes"), these three definitions are equivalent:

WSHttpBinding binding1 = new WSHttpBinding(  );
binding1.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

WSHttpBinding binding2 = new WSHttpBinding(SecurityMode.Message);
binding2.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

WSHttpBinding binding3 = new WSHttpBinding(  );
binding3.Security.Mode = SecurityMode.Message;
binding3.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

You can achieve the same configuration using a config file as follows:

<bindings>
   <wsHttpBinding>
      <binding name = "UserNameWS">
         <security mode = "Message">
            <message clientCredentialType = "UserName"/>
         </security>
      </binding>
   </wsHttpBinding>
</bindings>

Or, since Message security is the default, you can omit explicitly setting the mode in the config file:

<bindings>
   <wsHttpBinding>
      <binding name = "UserNameWS">
         <security>
            <message clientCredentialType = "UserName"/>
         </security>
      </binding>
   </wsHttpBinding>
</bindings>

Figure 10.4, "WSHttpBinding and security" shows the security-related elements of the WSHttpBinding.

Figure 10.4. WSHttpBinding and security

WSHttpBinding and security

WSHttpBinding has a reference to WSHttpSecurity, which uses the SecurityMode enum to indicate the transfer security mode. When Transport security is used, WSHttpSecurity will use an instance of HttpTransportSecurity. When Message security is used, WSHttpSecurity will use an instance of NonDualMessageSecurityOverHttp containing the client credentials type via the MessageCredentialType enum.

WSDualHttpBinding configuration

WSDualHttpBinding offers the Security property of the type WSDualHttpSecurity:

public class WSDualHttpBinding : Binding,...
{
   public WSDualHttpBinding(  );
   public WSDualHttpBinding(WSDualHttpSecurityMode securityMode);
   public WSDualHttpSecurity Security
   {get;}
   //More members
}

With WSDualHttpSecurity, you need to set the Mode property of the type WSDualHttpSecurityMode to WSDualHttpSecurityMode.Message. The Message property of WSDualHttpSecurity will then take effect:

public sealed class WSDualHttpSecurity
{
   public MessageSecurityOverHttp Message
   {get;}
   public WSDualHttpSecurityMode Mode
   {get;set;}
}

Message is of the type MessageSecurityOverHttp, presented earlier.

You need to set the ClientCredentialType property of MessageSecurityOverHttp to MessageCredentialType.Username. Recall that the default Message security credentials type of WSDualHttpBinding is Windows (see Table 10.3, "Bindings and Message security client credentials").

Because Message security is the default transfer security mode of the WSDualHttpBinding (see Table 10.1, "Bindings and transfer security modes"), these definitions are equivalent:

WSDualHttpBinding binding1 = new WSDualHttpBinding(  );
binding1.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

WSDualHttpBinding binding2 = new WSDualHttpBinding(WSDualHttpSecurityMode.Message);
binding2.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

WSDualHttpBinding binding3 = new WSDualHttpBinding(  );
binding3.Security.Mode = WSDualHttpSecurityMode.Message;
binding3.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

Here is the same configuration using a config file:

<bindings>
   <wsDualHttpBinding>
      <binding name = "WSDualWindowsSecurity">
         <security mode = "Message">
            <message  clientCredentialType = "UserName"/>
         </security>
      </binding>
   </wsDualHttpBinding>
</bindings>

Again, since Message security is the default, you can omit explicitly setting the mode in the config file:

<bindings>
   <wsDualHttpBinding>
      <binding name = "WSDualWindowsSecurity">
         <security>
            <message clientCredentialType = "UserName"/>
         </security>
      </binding>
   </wsDualHttpBinding>
</bindings>

Figure 10.5, "WSDualHttpBinding and security" shows the security-related elements of the WSDualHttpBinding.

Figure 10.5. WSDualHttpBinding and security

WSDualHttpBinding and security

WSDualHttpBinding has a reference to WSDualHttpSecurity, which uses the WSDualHttpSecurityMode enum to indicate the transfer security mode: Message or None. When Message security is used, WSDualHttpSecurity will use an instance of MessageSecurityOverHttp containing the client credentials type via the MessageCredentialType enum.

Message Protection

Since in the Internet scenario the client's message is transferred to the service over plain HTTP, it is vital to protect its content (both the client's credentials and the body of the message) by encrypting it. Encryption will provide for message integrity and privacy. One technical option for encryption is to use the client's password. However, WCF never uses this option, for a number of reasons. First, there are no guarantees that the password is strong enough, so anyone monitoring the communication could potentially break the encryption using a dictionary attack. Second, this approach forces the service (or more precisely, its host) to have access to the password, thus coupling the host to the credentials store. Finally, while the password may protect the message, it will not authenticate the service to the client.

Instead, to protect the message, WCF uses an X509 certificate. The certificate provides strong protection, and it authenticates the service to the client. A certificate works by using two keys, called the public and private keys, as well as a common name (CN) such as "MyCompanyCert." What is important about those keys is that anything encrypted with the public key can only be decrypted with the matching private one. The certificate contains the public key and the common name, and the private key is kept in some secure storage on the host machine to which the host has access. The host makes the certificate (and its public key) publicly available, so any client can access the host's endpoints and obtain the public key.

In a nutshell, what happens during a call is that WCF on the client's side uses the public key to encrypt all messages to the service. Upon receiving the encrypted message, WCF decrypts the message on the host side using the private key. Once the message is decrypted, WCF will read the client's credentials from the message, authenticate the client, and allow it to access the service. The real picture is a bit more complex, because WCF also needs to secure the reply messages and callbacks from the service to the client. One of the standards WCF supports deals with setting up such a secure conversation. In fact, several calls are made before the first request message from the client to the service, where WCF on the client's side generates a temporary shared secret it passes encrypted (using the service certificate) to the service. The client and the service will use that shared secret to protect all subsequent communication between them.

Configuring the host certificate

The ServiceHostBase class offers the Credentials property of the type ServiceCredentials. ServiceCredentials is a service behavior:

public abstract class ServiceHostBase : ...
{
   public ServiceCredentials Credentials
   {get;}
   //More members
}
public class ServiceCredentials : ...,IServiceBehavior
{
   public X509CertificateRecipientServiceCredential ServiceCertificate
   {get;}
   //More members
}

ServiceCredentials provides the ServiceCertificate property of the type X509CertificateRecipientServiceCredential:

public sealed class X509CertificateRecipientServiceCredential
{
   public void SetCertificate(StoreLocation storeLocation,
                              StoreName storeName,
                              X509FindType findType,
                              object findValue);
   //More members
}

You can use the SetCertificate( ) method to instruct WCF where and how to load the service certificate. You typically provide this information in the host config file as a custom behavior under the serviceCredentials section, as shown in Example 10.8, "Configuring the service certificate".

Example 10.8. Configuring the service certificate

<services>
   <service name = "MyService" behaviorConfiguration = "Internet">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "Internet">
         <serviceCredentials>
            <serviceCertificate
               findValue     = "MyServiceCert"
               storeLocation = "LocalMachine"
               storeName     = "My"
               x509FindType  = "FindBySubjectName"
            />
         </serviceCredentials>
      </behavior>
   </serviceBehaviors>
</behaviors>

Using the host certificate

The client developer can obtain the service certificate using any out-of-band mechanism (such as email, or via a public web page). The client can then include in its config file in the endpoint behavior section detailed information about the service certificate, such as where it is stored on the client side and how to find it. This is by far the most secure option from the client's perspective, because any attempt to subvert the client's address resolving and redirect the call to a malicious service will fail since the other service will not have the correct certificate. This is the least flexible option as well, however, because every time the client needs to interact with a different service, the client administrator will need to rework the client's config file.

A reasonable alternative to explicitly referencing the certificates of all services the client may interact with is to store those certificates in the client's Trusted People certificate folder. The administrator can then instruct WCF to allow calls only to services whose certificates are in that folder. In that case, the client will need to obtain the service certificate at runtime as part of the initial pre-call negotiation, check to see whether it is in the Trusted People store, and, if so, proceed to use it to protect the message. This certificate negotiation behavior is the default for the WS bindings. You can disable it and use a hard-configured certificate instead, but for the Internet scenario I strongly recommend using certificate negotiation and storing the certificates in the Trusted People store.

Service certificate validation

To instruct WCF as to what degree to validate and trust the service certificate, add a custom endpoint behavior to the client's config file. The behavior should use the clientCredentials section. ClientCredentials is an endpoint behavior that offers the ServiceCertificate property of the type X509CertificateRecipientClientCredential:

public class ClientCredentials : ...,IEndpointBehavior
{
   public X509CertificateRecipientClientCredential ServiceCertificate
   {get;}
   //More members
}

X509CertificateRecipientClientCredential offers the Authentication property of the type X509CertificateRecipientClientCredential:

public sealed class X509CertificateRecipientClientCredential
{
   public X509ServiceCertificateAuthentication Authentication
   {get;}
   //More members
}

X509CertificateRecipientClientCredential provides the CertificateValidationMode property of the enum type X509CertificateValidationMode:

public enum X509CertificateValidationMode
{
   None,
   PeerTrust,
   ChainTrust,
   PeerOrChainTrust,
   Custom
}

public class X509ServiceCertificateAuthentication
{
   public X509CertificateValidationMode CertificateValidationMode
   {get;set;}
   //More members
}

Example 10.9, "Validating the service certificate" demonstrates setting the service certificate validation mode in the client's config file.

Example 10.9. Validating the service certificate

<client>
   <endpoint behaviorConfiguration = "ServiceCertificate"
      ...
   </endpoint>
</client>
<behaviors>
   <endpointBehaviors>
      <behavior name = "ServiceCertificate">
         <clientCredentials>
            <serviceCertificate>
               <authentication certificateValidationMode = "PeerTrust"/>
            </serviceCertificate>
         </clientCredentials>
      </behavior>
   </endpointBehaviors>
</behaviors>

X509CertificateValidationMode.PeerTrust instructs WCF to trust the negotiated service certificate if it is present in the client's Trusted People store. X509CertificateValidationMode.ChainTrust instructs WCF to trust the certificate if it was issued by a root authority (such as VeriSign or Thwart) whose certificate is found in the client's Trusted Root Authority folder. X509CertificateValidationMode.ChainTrust is the default value used by WCF. X509CertificateValidationMode.PeerOrChainTrust allows either of those options. Since there are a number of illicit ways of obtaining a valid certificate from a public root authority, I do not recommend using this value. X509CertificateValidationMode.PeerOrChainTrust is available for tightly controlled environments that purge all public root authorities and install their own root certificates, which are used to sign other certificates.

Working with a test certificate

Developers often do not have access to their organizations' certificates, and therefore resort to using test certificates such as the ones generated by the MakeCert.exe command-line utility. There are two problems with test certificates. The first is that they will fail the default certificate validation on the client side, since the client uses X509CertificateValidationMode.ChainTrust by default. You can easily overcome this by installing the test certificate in the client's Trusted People store and using X509CertificateValidationMode.PeerTrust. The second problem is that WCF by default expects the service certificate name to match the service host's domain (or machine) name. This provides yet another line of defense, since typically with an Internet-facing service, the host domain name will match its certificate common name. To compensate, the client must explicitly specify the test certificate name in the endpoint identity's dns section:

<client>
   <endpoint
      address  = "http://localhost:8001/MyService"
      binding  = "wsHttpBinding"
      contract = "IMyContract">
      <identity>
         <dns value = "MyServiceCert"/>
      </identity>
   </endpoint>
</client>

Authentication

The client needs to provide its credentials to the proxy. The ClientCredentials property (presented earlier) of the ClientBase<T> base class has the UserName property of the type UserNamePasswordClientCredential:

public class ClientCredentials : ...,IEndpointBehavior
{
   public UserNamePasswordClientCredential UserName
   {get;}
   //More members
}

public sealed class UserNamePasswordClientCredential
{
   public string UserName
   {get;set;}
   public string Password
   {get;set;}
}

The client uses UserNamePasswordClientCredential to pass its username and password to the service, as demonstrated in Example 10.10, "Providing username and password credentials".

Example 10.10. Providing username and password credentials

MyContractClient proxy = new MyContractClient(  );

proxy.ClientCredentials.UserName.UserName = "MyUsername";
proxy.ClientCredentials.UserName.Password = "MyPassword";

proxy.MyMethod(  );
proxy.Close(  );
Tip
The client need not provide a domain name (if Windows security is used) or application name (if the ASP.NET providers are used). The host will use its service domain or a configured application name, as appropriate.

When working with a channel factory instead of a proxy class, you must set the Credentials property of the factory with the credentials:

ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>(");

factory.Credentials.UserName.UserName = "MyUsername";
factory.Credentials.UserName.Password = "MyPassword";

IMyContract proxy = factory.CreateChannel(  );
using(proxy as IDisposable)
{
   proxy.MyMethod(  );
}

Note that you cannot use the static CreateChannel( ) methods of ChannelFactory<T>, since you have to instantiate a factory in order to access the Credentials property.

Once the username and password credentials are received by the WCF on the service side, the host can choose to authenticate them as Windows credentials, ASP.NET membership provider's credentials, or even custom credentials. Whichever option you choose, make sure it matches your role-based policy configuration.

The ServiceCredentials class (available via the Credentials property of ServiceHostBase) provides the UserNameAuthentication property of the type UserNamePasswordServiceCredential:

public class ServiceCredentials : ...,IServiceBehavior
{
   public UserNamePasswordServiceCredential UserNameAuthentication
   {get;}
   //More members
}

UserNamePasswordServiceCredential has the UserNamePasswordValidationMode property of a matching enum type:

public enum UserNamePasswordValidationMode
{
   Windows,
   MembershipProvider,
   Custom
}
public sealed class UserNamePasswordServiceCredential
{
   public MembershipProvider MembershipProvider
   {get;set;}
   public UserNamePasswordValidationMode UserNamePasswordValidationMode
   {get; set;}
   //More members
}

By setting the UserNamePasswordValidationMode property, the host chooses how to authenticate the incoming username and password credentials.

Using Windows Credentials

While not necessarily common, WCF lets the Internet-facing service authenticate the incoming credentials as Windows credentials. To authenticate the client's username and password as Windows credentials, you need to set UserNamePasswordValidationMode to UserNamePasswordValidationMode.Windows. Because UserNamePasswordValidationMode.Windows is the default value of the UserNamePasswordValidationMode property, these two definitions are equivalent:

ServiceHost host1 = new ServiceHost(typeof(MyService));

ServiceHost host2 = new ServiceHost(typeof(MyService));
host2.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                                            UserNamePasswordValidationMode.Windows;

When using a config file, add a custom behavior that assigns the username and password authentication mode along with the service certificate information, as shown in Example 10.11, "Internet security with Windows credentials".

Example 10.11. Internet security with Windows credentials

<services>
   <service name = "MyService" behaviorConfiguration = "UsernameWindows">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "UsernameWindows">
         <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode = "Windows"/>
            <serviceCertificate
               ...
            />
         </serviceCredentials>
      </behavior>
   </serviceBehaviors>
</behaviors>

As with the programmatic case, adding this line to the config file:

<userNameAuthentication userNamePasswordValidationMode = "Windows"/>

is optional because it is the default setting.

Authorization

If the PrincipalPermissionMode property of ServiceAuthorizationBehavior is set to its default value of PrincipalPermissionMode.UseWindowsGroups, once the username and password are authenticated against Windows, WCF installs a Windows principal object and attaches it to the thread. This enables the service to freely use Windows NT groups for authorization, just as with the intranet case, both declaratively and programmatically.

Identity management

As long as the principal permission mode is set to PrincipalPermissionMode.UseWindowsGroups, the identity management aspect of the Internet scenario is just as with the intranet scenario, including the identities of the security call context, as shown in Table 10.4, "Identity management in the intranet scenario". The main difference between an intranet application and an Internet application that both use Windows credentials is that with the latter the client cannot dictate the allowed impersonation level, and the host can impersonate at will. This is because WCF will assign TokenImpersonationLevel.Impersonation to the Windows identity of the security call context.

Using the ASP.NET Providers

By default, role-based security in WCF uses Windows user groups for roles and Windows accounts for security identities. There are several drawbacks to this default policy. First, you may not want to assign a Windows account for every client of your Internet application. Second, the security policy is only as granular as the user groups in the hosting domain. Often you do not have control over your end customers' IT departments, and if you deploy your application in an environment in which the user groups are coarse or don't map well to the actual roles users play in your application, or if the group names are slightly different, Windows role-based security will be of little use to you. Role localization presents yet another set of challenges, because role names will likely differ between customer sites in different locales. Consequently, Internet applications hardly ever use Windows accounts and groups. Out of the box, .NET 2.0 (and later) provides a custom credential management infrastructure called the ASP.NET Providers. Despite its name, non-ASP.NET applications (such as WCF applications) can easily use it to authenticate users and authorize them, without ever resorting to Windows accounts.

One of the concrete implementations of the ASP.NET providers includes a SQL Server store. SQL Server is often the repository of choice for Internet applications, so I will use it in this scenario. To use the SQL Server provider, run the setup file aspnet_regsql.exe, found under %Windir%\Microsoft.NET\Framework\v2.0.50727\. The setup program will create a new database called aspnetdb, containing the tables and stored procedures required to manage the credentials.

The SQL Server credentials store is well designed and uses the latest best practices for credential management, such as password salting, stored procedures, normalized tables, and so on. In addition to providing a high-quality, secure solution, this infrastructure aids productivity, saving developers valuable time and effort. That said, the credential management architecture is that of a provider model, and you can easily add other storage options if required, such as an Access database.

The credentials providers

Figure 10.6, "The ASP.NET provider model" shows the architecture of the ASP.NET credentials providers.

Figure 10.6. The ASP.NET provider model

The ASP.NET provider model

Membership providers are responsible for managing users (usernames and passwords), and role providers are responsible for managing roles. Out of the box, ASP.NET offers support for membership stores in SQL Server or Active Directory, and roles can be stored in SQL Server, a file (the authorization store provider), or NT groups (the Windows token provider).

Username and password authentication is done using a class called MembershipProvider from the System.Web.Security namespace, defined as:

public abstract class MembershipProvider : ProviderBase
{
   public abstract string ApplicationName
   {get;set;}
   public abstract bool ValidateUser(string username,string password);
   //Additional members
}

MembershipProvider's goal is to encapsulate the actual provider used and the details of the actual data access, as well as to enable changing the membership provider without affecting the application itself. Depending on the configured security provider in the host config file, WCF will use a concrete data access class such as SqlMembershipProvider, targeting SQL Server or SQL Server Express:

public class SqlMembershipProvider : MembershipProvider
{...}

However, WCF interacts only with the MembershipProvider base functionality. WCF obtains the required membership provider by accessing the Provider static property of the Membership class, defined as:

public static class Membership
{
   public static string ApplicationName
   {get;set;}
   public static MembershipProvider Provider
   {get;}
   public static bool ValidateUser(string username,string password);
   //Additional members
}

Membership offers many members, which support the many aspects of user management. Membership.Provider retrieves the type of the configured provider from the System.Web section in the host config file. Unspecified, the role provider defaults to SqlMembershipProvider.

Tip
Because all membership providers derive from the abstract class MembershipProvider, if you write your own custom credential provider it needs to derive from MembershipProvider as well.

A single credentials store can serve many applications, and those applications may define the same usernames. To allow for that, every record in the credentials store is scoped by an application name (similar to the way usernames in Windows are scoped by a domain or machine name).

The ApplicationName property of Membership is used to set and retrieve the application name, and the ValidateUser( ) method is used to authenticate the specified credentials against the store, returning true if they match and false otherwise. Membership.ValidateUser( ) is shorthand for retrieving and using the configured provider.

If you have configured your application to use the ASP.NET credentials store for authorization and if you enabled roles support, after authentication WCF will install an instance of the internal class RoleProviderPrincipal and attach it to the thread invoking the operation:

sealed class RoleProviderPrincipal : IPrincipal
{...}

RoleProviderPrincipal uses the abstract class RoleProvider for authorization:

public abstract class RoleProvider : ProviderBase
{
   public abstract string ApplicationName
   {get;set;}
   public abstract bool IsUserInRole(string username,string roleName);
   //Additional members
}

The ApplicationName property of RoleProvider binds the role provider to the particular application. The IsUserInRole( ) method verifies the user's role membership. Just as all membership providers must derive from MembershipProvider, all role providers (including custom role providers) must derive from RoleProvider.

RoleProvider encapsulates the actual provider used, and the role provider to use is specified in the host config file. Depending on the configured role provider, RoleProviderPrincipal uses a corresponding data access class such as SqlRoleProvider to authorize the caller:

public class SqlRoleProvider : RoleProvider
{...}

You can obtain the required role provider by accessing the Provider static property of the Roles class, defined as:

public static class Roles
{
   public static string ApplicationName
   {get;set;}
   public static bool IsUserInRole(string username,string roleName);
   public static RoleProvider Provider
   {get;}
   //Additional members
}

Roles.IsUserInRole( ) is shorthand for first accessing Roles.Provider and then calling IsUserInRole( ) on it. Roles.Provider retrieves the type of the configured provider from the host config file. If unspecified, the role provider defaults to SqlRoleProvider.

Credentials administration

If you use SQL Server, .NET installs website administration pages under \Inetpub\wwwroot\aspnet_webadmin\<version number>. Developers can configure the application directly from within Visual Studio 2008. When you select ASP.NET Configuration from the Web Site menu, Visual Studio 2008 will launch the ASP.NET development server used for the administration pages, browse to the ASP.NET administration pages, and allow you to configure various parameters, including security. You can configure the following aspects for your application:

  • Create new users and delete existing ones

  • Create new roles and delete existing ones

  • Allocate users to roles

  • Retrieve a user's details

  • Set a user's status

  • Use additional features not relevant to this chapter

Shortcomings of Visual Studio 2008

There are a number of significant shortcomings to using the Visual Studio 2008-driven administration pages. First, you need Visual Studio 2008. It is unlikely that application or system administrators will have Visual Studio 2008, let alone know how to use it. The administration pages use "/" by default for the application name, and do not offer any visual way to modify that. Also, you must create a web application to activate the administration pages and there is no remote access: the application and Visual Studio 2008 must be co-located in order for Visual Studio 2008 to be able to access the application's configuration file, and the ASP.NET development server used for the administration pages cannot accept remote calls. The browser-based user interface is somewhat annoying (you need to frequently click the Back button) and rather dull. Furthermore, many features that administrators are likely to want to use are not available via the administration pages, despite the fact that the underlying provider classes support those features. Some of the things missing from the Visual Studio 2008-driven administration pages include the ability to:

  • Update most if not all of the details in a user account

  • Retrieve a user's password

  • Change a user's password

  • Reset a user's password

  • Retrieve information about the number of current online users

  • Remove all users from a role in one operation

  • Retrieve information about the password management policy (such as length, reset policy, type of passwords, etc.)

  • Test user credentials

  • Verify user role membership

There are additional features that administrators are likely to want, yet they are not supported even by the provider classes. These features include the ability to retrieve a list of all of the applications in the store, the ability to remove all users from an application, the ability to remove all roles from an application, the ability to delete an application (and all its associated users and roles), and the ability to delete all applications.

Tip
The IIS7 control panel applet also offers some administrative support for managing the roles and membership providers. However, this support is on a par with that of Visual Studio 2008.

Credentials Manager

This tools disparity motivated me to develop the Credentials Manager application, a smart client application that compensates for all of the shortcomings just listed. Figure 10.7, "The Credentials Manager utility" shows a screenshot of Credentials Manager.[8]

Figure 10.7. The Credentials Manager utility

The Credentials Manager utility

In Credentials Manager, which is available with ServiceModelEx, I wrapped the ASP.NET providers with a WCF service (which can be self-hosted or IIS 5/6- or WAS-hosted) and added the missing features, such as the ability to delete an application.

Credentials Manager uses the dedicated WCF service to administer the credentials store. In addition, it lets administrators select the address of the credentials service at runtime, and using the MetadataHelper class presented in Chapter 2, Service Contracts it verifies that the address provided does indeed support the required contracts.

Authentication

To authenticate the client's username and password using an ASP.NET provider, set the UserNamePasswordValidationMode property to UserNamePasswordValidationMode.MembershipProvider:

ServiceHost host = new ServiceHost(typeof(MyService));
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                                UserNamePasswordValidationMode.MembershipProvider;

Which provider is used depends on the host config file. In addition, the host config file must contain any provider-specific settings such as a SQL Server connection string, as shown in Example 10.12, "Internet security using an ASP.NET SQL Server provider".

Example 10.12. Internet security using an ASP.NET SQL Server provider

<connectionStrings>
   <add name= "AspNetDb" connectionString = "data source=(local);
                               Integrated Security=SSPI;Initial Catalog=aspnetdb"/>
</connectionStrings>

<system.serviceModel>
   <services>
      <service name = "MyService" behaviorConfiguration = "ASPNETProviders">
         <endpoint
            ...
         />
      </service>
   </services>
   <behaviors>
      <serviceBehaviors>
         <behavior name = "ASPNETProviders">
            <serviceCredentials>
               <userNameAuthentication
                  userNamePasswordValidationMode = "MembershipProvider"/>
               <serviceCertificate
                  ...
               />
            </serviceCredentials>
         </behavior>
      </serviceBehaviors>
   </behaviors>
</system.serviceModel>

The default application name will be a useless /, so you must assign your application's name. Once the ASP.NET providers are configured, WCF initializes the MembershipProvider property of UserNamePasswordServiceCredential with an instance of the configured membership provider. You can programmatically access that membership provider and set its application name:

ServiceHost host = new ServiceHost(typeof(MyService));
Debug.Assert(host.Credentials.UserNameAuthentication.MembershipProvider != null);
Membership.ApplicationName = "MyApplication";
host.Open(  );

You can also configure the application name in the config file, but for that you need to define a custom ASP.NET membership provider, as shown in Example 10.13, "Configuring the application name for the membership provider".

Example 10.13. Configuring the application name for the membership provider

<system.web>
   <membership defaultProvider = "MySqlMembershipProvider">
      <providers>
         <add name = "MySqlMembershipProvider"
            type = "System.Web.Security.SqlMembershipProvider"
            connectionStringName = "AspNetDb"
            applicationName = "MyApplication"
         />
      </providers>
   </membership>
</system.web>
<connectionStrings>
   <add name = "AspNetDb"
      ...
   />
</connectionStrings>

First, you add a system.Web section with a providers section, where you add a custom membership provider and set that to be the new default membership provider. Next, you need to list the fully qualified type name of the new provider. Nothing prevents you from referencing an existing implementation of a membership provider (such as SqlMembershipProvider, as in Example 10.13, "Configuring the application name for the membership provider"). When using the SQL provider, you must also list the connection string to use, and you cannot rely on the default connection string from machine.config. Most importantly, you must set the ApplicationName tag to the desired application name.

Authorization

To support authorizing the users, the host must enable role-based security by adding this to the config file:

<system.web>
   <roleManager enabled = "true"/>
</system.web>
Tip
To enable the role manager programmatically, you have to use reflection.

Enabling roles this way will initialize the Roles class and have its Provider property set to the configured provider. To use the ASP.NET role provider, set the PrincipalPermissionMode property to PrincipalPermissionMode.UseAspNetRoles:

ServiceHost host = new ServiceHost(typeof(MyService));
host.Authorization.PrincipalPermissionMode =
                                         PrincipalPermissionMode.UseAspNetRoles;
host.Open(  );

Alternatively, when using a config file, you can add a custom behavior to that effect:

<services>
   <service name = "MyService" behaviorConfiguration = "ASPNETProviders">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "ASPNETProviders">
         <serviceAuthorization principalPermissionMode = "UseAspNetRoles"/>
         ...
      </behavior>
   </serviceBehaviors>
</behaviors>

After authenticating the client, the RoleProvider property of ServiceAuthorizationBehavior will be set to the configured role provider:

public sealed class ServiceAuthorizationBehavior : IServiceBehavior
{
   public RoleProvider RoleProvider
   {get;set;}
   //More members
}

The default application name will be a useless /, so you must assign your application's name using the static helper class Roles:

ServiceHost host = new ServiceHost(typeof(MyService));
Debug.Assert(host.Credentials.UserNameAuthentication.MembershipProvider != null);
Roles.ApplicationName = "MyApplication";

You can also configure the application name in the config file, but for that you need to define a custom ASP.NET role provider, as shown in Example 10.14, "Configuring the application name for the role provider".

Example 10.14. Configuring the application name for the role provider

<system.web>
   <roleManager enabled = "true" defaultProvider = "MySqlRoleManager">
      <providers>
         <add name = "MySqlRoleManager"
            type = "System.Web.Security.SqlRoleProvider"
            connectionStringName = "AspNetDb"
            applicationName = "MyApplication"
         />
      </providers>
   </roleManager>
</system.web>
<connectionStrings>
   <add name = "AspNetDb"
      ...
   />
</connectionStrings>

As with the membership provider, you add a system.Web section with a providers section, where you add a custom role provider and set that to be the new default role provider. Next you need to list the fully qualified type name of the new provider. As with the membership provider, you can reference any existing implementation of a role provider, such as SqlRoleProvider, in which case you must also list the connection string to use. Finally, you must set the ApplicationName tag to the desired application name.

Declarative role-based security

You can use the PrincipalPermission attribute to verify role membership just as in the intranet scenario, because all the attribute does is access the principal object attached to the thread, which WCF has already set to RoleProviderPrincipal. Example 10.15, "ASP.NET role provider declarative role-based security" demonstrates declarative role-based security using the ASP.NET providers.

Example 10.15. ASP.NET role provider declarative role-based security

class MyService : IMyContract
{
   [PrincipalPermission(SecurityAction.Demand,Role = "Manager")]
   public void MyMethod(  )
   {...}
}

Identity Management

In the Internet scenario, when you use the ASP.NET providers, the identity associated with the principal object is a GenericIdentity that wraps the username provided by the client. That identity is considered authenticated. The security call context's primary identity will match the principal identity. The Windows identity, on the other hand, will be set to a Windows identity with a blank username; that is, it is unauthenticated. Table 10.5, "Identity management in the Internet scenario with ASP.NET providers" shows the identities in this scenario.

Table 10.5. Identity management in the Internet scenario with ASP.NET providers

Identity

Type

Value

Authenticated

Thread principal

GenericIdentity

Username

Yes

Security context primary

GenericIdentity

Username

Yes

Security context Windows

WindowsIdentity

-

No


Impersonation

Since no valid Windows credentials are provided, the service cannot impersonate any of its clients.

Callbacks

When you use the ASP.NET providers, while the callback message is protected, all calls into the callback object come in with an unauthenticated principal. As a result, the principal identity will be set to a Windows identity with a blank username, which will preclude authorization and role-based security, as it is considered anonymous. Also, while the callback does have a security call context, the Windows identity will similarly be set to a WindowsIdentity instance with a blank identity, which will preclude impersonation. The only meaningful information will be in the primary identity, which will be set to an instance of the X509Identity class, with the name set to the common name of the service host certificate suffixed by a thumbprint (a hash) of the certificate:

class MyClient : IMyContractCallback
{
   public void OnCallback(  )
   {
      IPrincipal principal = Thread.CurrentPrincipal;
      Debug.Assert(principal.Identity.IsAuthenticated == false);

      ServiceSecurityContext context = ServiceSecurityContext.Current;
      Debug.Assert(context.PrimaryIdentity.Name ==
                   "CN=MyServiceCert; D6E33B50BCF6D9609E68762F2C6A14F65679268B");
      Debug.Assert(context.IsAnonymous == false);
   }
}

I recommend avoiding any sensitive work in the callback, since you cannot easily use role-based security.

In the business-to-business scenario, the service and its clients are disparate business entities. They do not share credentials or accounts, and the communication between them is typically closed to the public. There are relatively few clients interacting with the service, and the client can only interact with the service after an elaborate business agreement has been established and other conditions have been met. Instead of Windows accounts or usernames, the clients identify themselves to the service using X509 certificates. These certificates are usually known a priori to the service. The client or service may not necessarily be using WCF, or even Windows. Therefore, if you are writing a service or a client, you cannot assume the use of WCF at the other end. The client calls originate from outside the firewall, and you need to rely on HTTP for transport. Also, multiple intermediaries are possible.

Securing the Business-to-Business Bindings

For the business-to-business scenario, you should use the Internet bindings; namely, BasicHttpBinding, WSHttpBinding, and WSDualHttpBinding. You must use Message security for the transfer security mode, to provide for end-to-end security across all intermediaries. The message will be protected using a service-side certificate, just as with the Internet scenario. However, unlike with the Internet scenario, here the clients provide credentials in the form of a certificate. This is done uniformly across these bindings by selecting MessageCredentialType.Certificate for the client credentials type to be used with the Message security mode. You need to configure this on both the client and the service. For example, to configure the WSHttpBinding programmatically, you would write:

WSHttpBinding binding = new WSHttpBinding(  );
binding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;

Or with a config file:

<bindings>
   <wsHttpBinding>
      <binding name = "WSCertificateSecurity">
         <security mode = "Message">
            <message clientCredentialType = "Certificate"/>
         </security>
      </binding>
   </wsHttpBinding>
</bindings>

Authentication

The service administrator has a number of options as to how to authenticate the certificates sent by the clients. If its certificate is validated, the client is considered authenticated. If no validation is done on the service side, merely sending a certificate will do. If the validation mode is set to use a chain of trust and a trusted root authority issued the certificate, the client will be considered authenticated. However, the best way of validating the client's certificate is to use peer trust. With this approach, the service administrator installs the certificates of all the clients allowed to interact with the service in the Trusted People store on the service's local machine. When the service receives the client's certificate, it verifies that the certificate is in the trusted store, and if so, the client is considered authenticated. I recommend using peer trust in the business-to-business scenario.

The ServiceCredentials class offers the ClientCertificate property of the type X509CertificateInitiatorServiceCredential:

public class ServiceCredentials : ...,IServiceBehavior
{
   public X509CertificateInitiatorServiceCredential ClientCertificate
   {get;}
   //More members
}

X509CertificateInitiatorServiceCredential provides the Authentication property of the type X509ClientCertificateAuthentication, which lets you configure the certificate validation mode:

public sealed class X509CertificateInitiatorServiceCredential
{
   public X509ClientCertificateAuthentication Authentication
   {get;}
   //More members
}
public class X509ClientCertificateAuthentication
{
   public X509CertificateValidationMode CertificateValidationMode
   {get;set;} //More members
}

Example 10.16, "Configuring the host for business-to-business security" demonstrates the settings required in the host config file for the business-to-business scenario. Note in Example 10.16, "Configuring the host for business-to-business security" that the host still needs to provide its own certificate for Message security.

Example 10.16. Configuring the host for business-to-business security

<services>
   <service name = "MyService" behaviorConfiguration = "BusinessToBusiness">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "BusinessToBusiness">
         <serviceCredentials>
            <serviceCertificate
               ...
            />
            <clientCertificate>
               <authentication certificateValidationMode = "PeerTrust"/>
            </clientCertificate>
         </serviceCredentials>
      </behavior>
   </serviceBehaviors>
</behaviors>

The client needs to reference the certificate to use by including its location, name, and lookup method. This is done by accessing the ClientCredentials property of the proxy, which offers the ClientCertificate property of the type X509CertificateInitiatorClientCredential:

public class ClientCredentials : ...,IEndpointBehavior
{
   public X509CertificateInitiatorClientCredential ClientCertificate
   {get;}
   //More members
}
public sealed class X509CertificateInitiatorClientCredential
{
   public void SetCertificate(StoreLocation storeLocation,
                              StoreName storeName,
                              X509FindType findType,
                              object findValue);
   //More members
}

However, the client will typically set these values in its config file, as shown in Example 10.17, "Setting the client's certificate".

Example 10.17. Setting the client's certificate

<client>
   <endpoint behaviorConfiguration = "BusinessToBusiness"
      ...
   />
</client>
   ...
<behaviors>
   <endpointBehaviors>
      <behavior name = "BusinessToBusiness">
         <clientCredentials>
            <clientCertificate
               findValue     = "MyClientCert"
               storeLocation = "LocalMachine"
               storeName     = "My"
               x509FindType  = "FindBySubjectName"
            />
            ...
         </clientCredentials>
      </behavior>
   </endpointBehaviors>
</behaviors>

The config file must also indicate the service certificate validation mode. When using the BasicHttpBinding, since that binding cannot negotiate the service certificate, the client's config file needs to contain in the service certificate section of the endpoint behavior the location of the service certificate to use. Note that when using a service test certificate, as with the Internet scenario, the client's config file must still include the information regarding the endpoint's identity.

If the client is required to always provide the same certificate, the client developer can encapsulate setting the certificate in the proxy constructors:

class MyContractClient: ClientBase<...>,...
{
   public MyContractClient(  )
   {
      SetCertificate(  );
   }
   /* More constructors */

   void SetCertificate(  )
   {
      ClientCredentials.ClientCertificate.SetCertificate(
                                                   StoreLocation.LocalMachine,
                                                   StoreName.My,
                                                   X509FindType.FindBySubjectName,
                                                   "MyClientCert");
   }
   //Rest of the proxy
}

Once the client certificate is configured, there is no need to do anything special with the proxy class:

MyContractClient proxy = new MyContractClient(  );
proxy.MyMethod(  );
proxy.Close(  );

Authorization

By default, the service cannot employ principal-based, role-based security. The reason is that the credentials provided-namely, the client's certificate-do not map to either Windows or ASP.NET user accounts. Because business-to-business endpoints and services are often dedicated to a small set of clients or even a particular client, this lack of authorization support may not pose a problem. If that is indeed your case, you should set the PrincipalPermissionMode property to PrincipalPermissionMode.None, so that WCF will attach a generic principal with a blank identity as opposed to a WindowsIdentity instance with a blank identity.

If, on the other hand, you would still like to authorize the clients, you can actually achieve just that. In essence, all you need to do is deploy some credentials store, add each client's certificate name-that is, its common name and its thumbprint-to that repository, and then perform access checks against that store as needed.

In fact, nothing prevents you from taking advantage of the ASP.NET role provider for authorization, even if you didn't use the membership provider for authentication. This ability to use the providers separately was a core design goal for the ASP.NET provider model.

First, you need to enable the role provider in the host config file and configure the application name as in Example 10.14, "Configuring the application name for the role provider" (or provide the application name programmatically).

Next, add the client certificate and thumbprint to the membership store as a user, and assign roles to it. For example, when using a certificate whose common name is MyClientCert, you need to add a user by that name (such as "CN=MyClientCert; 12A06153D25E94902F50971F68D86DCDE2A00756") to the membership store, and provide a password. The password, of course, is irrelevant and will not be used. Once you have created the user, assign it to the appropriate roles in the application.

Most importantly, set the PrincipalPermissionMode property to PrincipalPermissionMode.UseAspNetRoles. Example 10.18, "ASP.NET role-based security for the business-to-business scenario" lists the required settings in the host config file.

Example 10.18. ASP.NET role-based security for the business-to-business scenario

<system.web>   <roleManager enabled = "true" defaultProvider = "...">      ...   </roleManager></system.web>

<system.serviceModel>
   <services>
      <service name = "MyService" behaviorConfiguration = "BusinessToBusiness">
         ...
      </service>
   </services>
   <behaviors>
      <serviceBehaviors>
         <behavior name = "BusinessToBusiness">
            <serviceCredentials>
               <serviceCertificate
                  ...
               />
               <clientCertificate>
                  <authentication certificateValidationMode = "PeerTrust"/>
               </clientCertificate>
            </serviceCredentials>
            <serviceAuthorization principalPermissionMode = "UseAspNetRoles"/>
         </behavior>
      </serviceBehaviors>
   </behaviors>
   <bindings>
      ...
   </bindings>
</system.serviceModel>

Now you can use role-based security, just as in Example 10.15, "ASP.NET role provider declarative role-based security".

Identity Management

If the PrincipalPermissionMode property is set to PrincipalPermissionMode.None, then the principal identity will be a GenericIdentity with a blank username. The security call context's primary identity will be of the type X509Identity and will contain the client certificate's common name and its thumbprint. The security call context's Windows identity will have a blank username, since no valid Windows credentials were provided. If the PrincipalPermissionMode property is set to PrincipalPermissionMode.UseAspNetRoles, then both the principal identity and the security call context's primary identity will be set to an instance of X509Identity containing the client certificate and thumbprint. The security call context's Windows identity will have a blank username, as before. Table 10.6, "Identity management in the business-to-business scenario with ASP.NET role providers" details this setup.

Table 10.6. Identity management in the business-to-business scenario with ASP.NET role providers

Identity

Type

Value

Authenticated

Thread principal

X509Identity

Client cert name

Yes

Security context primary

X509Identity

Client cert name

Yes

Security context Windows

WindowsIdentity

-

No


Impersonation

Since no valid Windows credentials are provided, the service cannot impersonate any of its clients.

Callbacks

In the business-to-business scenario callbacks behave just as they do in the Internet scenario, since in both cases the same transfer security mechanism is used and the service identifies itself using a certificate. As with the Internet callback scenario, avoid sensitive work in the callback, since you cannot use role-based security.

Host Security Configuration

While Figure 10.8, "The security elements of ServiceHostBase" is not specific to the business-to-business scenario, having covered this scenario, this is the first point in this chapter where I can show all the pieces of the service host pertaining to security.

Figure 10.8. The security elements of ServiceHostBase

The security elements of ServiceHostBase

In the anonymous scenario, the clients access the service without presenting any credentials-they are anonymous. Nevertheless, the clients and the service do require secure message transfer, impervious to tampering and sniffing. Both Internet-facing and intranet-based applications may need to provide for anonymous yet end-to-end secure access. The anonymous scenario can have any number of clients, small or large. The clients may connect over HTTP, TCP, or MSMQ.

Securing the Anonymous Bindings

The need to secure the message and the fact that the clients may be calling over the Internet with multiple intermediaries mean that in the anonymous scenario you should use Message security. With Message security, you can easily satisfy both requirements by setting the ClientCredentialType property to MessageCredentialType.None. The service needs to be configured with a certificate to secure the message itself. For the anonymous scenario, you can use only the WSHttpBinding, WSDualHttpBinding, NetTcpBinding, and NetMsmqBinding-a mixture of both Internet and intranet bindings, as is required in this scenario. You cannot use the NetNamedPipeBinding or the BasicHttpBinding, as the former does not support Message security and the latter does not support having no credentials in the message (see Table 10.1, "Bindings and transfer security modes" and Table 10.3, "Bindings and Message security client credentials").

Configuring the allowed bindings is done similarly to the previous scenarios. The noticeable difference is in configuring for no client credentials. For example, here's how to configure the WSHttpBinding:

WSHttpBinding binding = new WSHttpBinding(  );
binding.Security.Message.ClientCredentialType = MessageCredentialType.None;

You can also do this using a config file:

<bindings>
   <wsHttpBinding>
      <binding name = "WSAnonymous">
         <security>
            <message clientCredentialType = "None"/>
         </security>
      </binding>
   </wsHttpBinding>
</bindings>

Authentication

No client authentication is done in the anonymous scenario, of course, and the client need not provide any credentials to the proxy. For the purposes of service authentication to the client and message protection, the service needs to provide its certificate, as in Example 10.8, "Configuring the service certificate".

Authorization

Since the clients are anonymous (and unauthenticated), authorization and role-based security are precluded. The service host should set the PrincipalPermissionMode property to PrincipalPermissionMode.None to have WCF install a generic principal with a blank identity, instead of a Windows principal with a blank identity.

Identity Management

Assuming the use of PrincipalPermissionMode.None, the identity associated with the principal object is a GenericIdentity with a blank username. That identity is considered unauthenticated. The security call context's primary identity will match the principal identity. The Windows identity, on the other hand, will be set to a Windows identity with a blank username-that is, it will be unauthenticated. Table 10.7, "Identity management in the anonymous scenario" shows the identities in this scenario.

Table 10.7. Identity management in the anonymous scenario

Identity

Type

Value

Authenticated

Thread principal

GenericIdentity

-

No

Security context primary

GenericIdentity

-

No

Security context Windows

WindowsIdentity

-

No


Impersonation

Since the clients are anonymous, the service cannot impersonate any of its clients.

Callbacks

While the call from the client to the service is anonymous, the service does reveal its identity to the client. The primary identity of the security call context will be set to an instance of the X509Identity class, with the name set to the common name of the service host certificate suffixed by the certificate's thumbprint. The rest of the information is masked out. The principal identity will be set to a Windows identity with a blank username, which will preclude authorization and role-based security, as it is considered anonymous. The security call context's Windows identity will be set to a WindowsIdentity instance with a blank identity, which will preclude impersonation. Avoid sensitive work in the callback, since you cannot use role-based security.

In this last scenario, your application turns off security completely. The service does not rely on any transfer security, and it does not authenticate or authorize its callers. Obviously, such a service is completely exposed, and you generally need a very good business justification for relinquishing security. Both Internet and intranet services can be configured for no security, and they can accept any number of clients.

Unsecuring the Bindings

To turn off security, you need to set the transfer security mode to None. This will also avoid storing any client credentials in the message. All bindings support no transfer security (see Table 10.1, "Bindings and transfer security modes").

Configuring the allowed bindings is done similarly to the previous scenarios, except the transfer security mode is set to None. For example, here's how to configure the NetTcpBinding programmatically:

NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);

And here's how to do this using a config file:

<bindings>
   <netTcpBinding>
      <binding name = "NoSecurity">
         <security mode = "None"/>
      </binding>
   </netTcpBinding>
</bindings>

Authentication

No client authentication is done in this scenario, and the client does not need to provide any credentials to the proxy. Nor does the client ever authenticate the service.

Authorization

Since the clients are anonymous (and unauthenticated), authorization and role-based security are precluded. WCF will automatically set the PrincipalPermissionMode property to PrincipalPermissionMode.None to install a generic principal with a blank identity.

Identity Management

The identity associated with the principal object is a GenericIdentity with a blank username. That identity is considered unauthenticated. Unlike all the previous scenarios, in the no security scenario, the operation has no security call context, and the ServiceSecurityContext.Current returns null. Table 10.8, "Identity management in the no security scenario" shows the identities in this scenario.

Table 10.8. Identity management in the no security scenario

Identity

Type

Value

Authenticated

Thread principal

GenericIdentity

-

No

Security context primary

-

-

-

Security context Windows

-

-

-


Impersonation

Because the clients are anonymous, the service cannot impersonate any of its clients.

Callbacks

Unlike in all the previous scenarios, in the absence of transfer security, callbacks come in under the client's own identity. The principal identity will be set to an instance of WindowsIdentity with the client's username. The callback will be authenticated, but there is no point in either impersonation or using role-based security since the client will only be authorizing itself. In addition, the security call context of the callback will be set to null.

Now that you have seen the making of the five key scenarios, Table 10.9, "Bindings and security scenarios" and Table 10.10, "The security aspects of the various scenarios" serve as a summary of their key elements. Table 10.9, "Bindings and security scenarios" lists the bindings used in each scenario. Note again that while technically you could use other bindings in almost all of the scenarios, my binding selections are aligned with the contexts in which the scenarios are used.

Table 10.9. Bindings and security scenarios

Binding

Intranet

Internet

B2B

Anonymous

None

BasicHttpBinding

No

No

Yes

No

Yes

NetTcpBinding

Yes

Yes

No

Yes

Yes

NetNamedPipeBinding

Yes

No

No

No

Yes

WSHttpBinding

No

Yes

Yes

Yes

Yes

WSDualHttpBinding

No

Yes

Yes

Yes

Yes

NetMsmqBinding

Yes

No

No

Yes

Yes


Table 10.10, "The security aspects of the various scenarios" shows how each of the security aspects defined at the beginning of this chapter (transfer security, service and client authentication, authorization, and impersonation) relates to each scenario.

Table 10.10. The security aspects of the various scenarios

Aspect

Intranet

Internet

B2B

Anonymous

None

Transport security

Yes

No

No

No

No

Message security

No

Yes

Yes

Yes

No

Service authentication

Windows

Certificate

Certificate

Certificate

No

Client authentication

Windows

ASP.NET

Certificate

No

No

Authorization

Windows

ASP.NET

No/ASP.NET

No

No

Impersonation

Yes

No

No

No

No


WCF security is truly a vast topic. The number of details to master is daunting, and intricate relationships exist between the various aspects. The programming model is very complex, and at first you're likely to have an inescapable feeling of navigating a maze. To make things even worse, getting it wrong has severe implications both at the application and the business level. To simplify things, I came up with a declarative security framework for WCF. For the service, I have provided a security attribute (with matching support for the host), and for the client I have provided a few helper classes and secure proxy classes. My declarative framework grossly simplifies WCF security configuration, placing it on a par with other aspects of WCF configuration such as transactions and synchronization. My goal was to provide a declarative model that would be simple to use and would minimize the need to understand the many details of security. As a developer, all you need to do is select the correct scenario (out of the five common scenarios discussed in this chapter), and my framework will automate the configuration. Not only that, but my framework mandates the correct options and enforces my recommendations. At the same time, my model maintains granularity and allows developers to control the underlying configuration if the need arises.

The SecurityBehaviorAttribute

Example 10.19, "The SecurityBehaviorAttribute" lists the definition of the SecurityBehaviorAttribute and the ServiceSecurity enum. ServiceSecurity defines the five scenarios supported by my framework.

Example 10.19. The SecurityBehaviorAttribute

public enum ServiceSecurity
{
   None,
   Anonymous,
   BusinessToBusiness,
   Internet,
   Intranet
}
[AttributeUsage(AttributeTargets.Class)]
public class SecurityBehaviorAttribute : Attribute,IServiceBehavior
{
   public SecurityBehaviorAttribute(ServiceSecurity mode);
   public SecurityBehaviorAttribute(ServiceSecurity mode,
                                    string serviceCertificateName);
   public SecurityBehaviorAttribute(ServiceSecurity mode,
                                    StoreLocation storeLocation,
                                    StoreName storeName,
                                    X509FindType findType,
                                    string serviceCertificateName);
   public bool ImpersonateAll
   {get;set;}
   public string ApplicationName
   {get;set;}
   public bool UseAspNetProviders
   {get;set;}
}

When applying the SecurityBehavior attribute, you need to provide it with the target scenario in the form of a ServiceSecurity value. You can use just the constructors of the SecurityBehavior attribute, or you can set the properties. Unset, the properties all default to reasonable values in the context of the target scenario. When selecting a scenario, the configured behavior follows to the letter my previous descriptions of the individual scenarios. The SecurityBehavior attribute yields a composable security model, allowing quite a few permutations and sub-scenarios. When using the attribute, you can even have a security-free host config file, or you can combine settings from the config file with values driven by the attribute. Similarly, your hosting code can be free of security, or you can combine programmatic host security with the attribute.

Configuring an intranet service

To configure a service for the intranet security scenario, apply SecurityBehavior with ServiceSecurity.Intranet:

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod(  );
}
[SecurityBehavior(ServiceSecurity.Intranet)]
class MyService : IMyContract
{
   public void MyMethod(  )
   {...}
}

Even though the service contract used may not constrain the protection level, the attribute programmatically adds that demand to enforce message protection. You can use Windows NT groups for role-based security:

[SecurityBehavior(ServiceSecurity.Intranet)]
class MyService : IMyContract
{
   [PrincipalPermission(SecurityAction.Demand,Role = "Customer")]
   public void MyMethod(  )
   {...}
}

The service can programmatically impersonate the callers, or use the operation behavior attribute for individual method impersonation. You can also configure the service to automatically impersonate all callers in all methods via the ImpersonateAll property. ImpersonateAll defaults to false, but when it's set to true the attribute will impersonate all callers in all operations without your needing to apply any operation behavior attributes or do any host configuration:

[SecurityBehavior(ServiceSecurity.Intranet,ImpersonateAll = true)]
class MyService : IMyContract
{...}

Configuring an Internet service

With the Internet scenario, you need to both configure the service for this scenario and select the service certificate to use. Note in Example 10.19, "The SecurityBehaviorAttribute" that the ServiceBehavior attribute constructor may take the service certificate name. If it's unspecified, the service certificate is loaded from the host config file as with Example 10.8, "Configuring the service certificate":

[SecurityBehavior(ServiceSecurity.Internet)]
class MyService : IMyContract
{...}

You can also specify the service certificate name, in which case the specified certificate is loaded from the LocalMachine store from the My folder by name:

[SecurityBehavior(ServiceSecurity.Internet,"MyServiceCert")]
class MyService : IMyContract
{...}

If the certificate name is set to an empty string, the SecurityBehavior attribute will infer the certificate name by using the hosting machine name (or domain) for the certificate name and load such a certificate from the LocalMachine store from the My folder by name:

[SecurityBehavior(ServiceSecurity.Internet,")]
class MyService : IMyContract
{...}

Finally, the attribute lets you explicitly specify the store location, the store name, and the lookup method:

[SecurityBehavior(ServiceSecurity.Internet,
                  StoreLocation.LocalMachine,StoreName.My,
                  X509FindType.FindBySubjectName,"MyServiceCert")]
class MyService : IMyContract
{...}

Note that you can combine an explicit location with an inferred certificate name:

[SecurityBehavior(ServiceSecurity.Internet,
                  StoreLocation.LocalMachine,StoreName.My,
                  X509FindType.FindBySubjectName,")]
class MyService : IMyContract
{...}

Which credentials store to authenticate the client against is indicated by the UseAspNetProviders property. UseAspNetProviders defaults to false, meaning that the default is to authenticate the client's username and password as Windows credentials (as in Example 10.11, "Internet security with Windows credentials"). Because of that, when UseAspNetProviders is false you can by default use Windows NT groups for authorization and even impersonate all callers:

[SecurityBehavior(ServiceSecurity.Internet,"MyServiceCert",ImpersonateAll = true)]
class MyService : IMyContract
{...}

If UseAspNetProviders is set to true, instead of Windows credentials the SecurityBehavior attribute will use the ASP.NET membership and role providers, as prescribed for the Internet scenario:

[SecurityBehavior(ServiceSecurity.Internet,"MyServiceCert",
                  UseAspNetProviders = true)]
class MyService : IMyContract
{
   [PrincipalPermission(SecurityAction.Demand,Role = "Manager")]
   public void MyMethod(  )
   {...}
}
Tip
The attribute will programmatically enable the role manager section in the config file.

The SecurityBehavior attribute allows the use of the NetTcpBinding with ServiceSecurity.Internet along with ASP.NET providers to allow intranet applications to avoid using Windows accounts and groups, as explained previously.

Next is the issue of supplying the application name for the ASP.NET providers. That is governed by the ApplicationName property. If no value is assigned, the SecurityBehavior attribute will look up the application name from the config file, as in Example 10.13, "Configuring the application name for the membership provider" and Example 10.14, "Configuring the application name for the role provider". If no value is found in the host config file, the attribute will not default to using the meaningless / from machine.config; instead, it will by default use the host assembly name for the application name. If the ApplicationName property is assigned a value, that value will override whatever application name is present in the host config file:

[SecurityBehavior(ServiceSecurity.Internet,"MyServiceCert",
                  UseAspNetProviders = true,ApplicationName = "MyApplication")]
class MyService : IMyContract
{...}

Configuring a business-to-business service

To configure a service for the business-to-business scenario, you must set ServiceSecurity to ServiceSecurity.BusinessToBusiness. The SecurityBehavior attribute will use peer trust for validating the client's certificate. Configuring the service certificate is done just as with ServiceSecurity.Internet. For example:

[SecurityBehavior(ServiceSecurity.BusinessToBusiness)]
class MyService : IMyContract
{...}

[SecurityBehavior(ServiceSecurity.BusinessToBusiness,")]
class MyService : IMyContract
{...}


[SecurityBehavior(ServiceSecurity.BusinessToBusiness,"MyServiceCert")]
class MyService : IMyContract
{...}

By default, with ServiceSecurity.BusinessToBusiness, the attribute will set the PrincipalPermissionMode property of the host to PrincipalPermissionMode.None, and the service will not be able to authorize its callers. However, setting the UseAspNetProviders property to true will enable use of the ASP.NET role providers, as in Example 10.18, "ASP.NET role-based security for the business-to-business scenario":

[SecurityBehavior(ServiceSecurity.BusinessToBusiness,UseAspNetProviders = true)]
class MyService : IMyContract
{...}

When using the ASP.NET role providers, the application name is looked up and decided upon just as with ServiceSecurity.Internet:

[SecurityBehavior(ServiceSecurity.BusinessToBusiness,"MyServiceCert",
                  UseAspNetProviders = true,ApplicationName = "MyApplication")]
class MyService : IMyContract
{...}

Configuring an anonymous service

To allow anonymous callers, you need to configure the attribute with ServiceSecurity.Anonymous. Configuring the service certificate is done just as with ServiceSecurity.Internet. For example:

[SecurityBehavior(ServiceSecurity.Anonymous)]
class MyService : IMyContract
{...}

[SecurityBehavior(ServiceSecurity.Anonymous,")]
class MyService : IMyContract
{...}

[SecurityBehavior(ServiceSecurity.Anonymous,"MyServiceCert")]
class MyService : IMyContract
{...}

Configuring a no-security service

To turn off security completely, provide the attribute with ServiceSecurity.None:

[SecurityBehavior(ServiceSecurity.None)]
class MyService : IMyContract
{...}

Implementing the SecurityBehavior attribute

Example 10.20, "Implementing SecurityBehaviorAttribute" is a partial listing of the implementation of SecurityBehaviorAttribute.

Example 10.20. Implementing SecurityBehaviorAttribute

[AttributeUsage(AttributeTargets.Class)]
class SecurityBehaviorAttribute : Attribute,IServiceBehavior
{
   SecurityBehavior m_SecurityBehavior;

   public bool ImpersonateAll
   {get;set;}
   public string ApplicationName
   {get;set;}
   public bool UseAspNetProviders
   {get;set;}

   public SecurityBehaviorAttribute(ServiceSecurity mode)
   {
      m_SecurityBehavior = new SecurityBehavior(mode);
   }
   public SecurityBehaviorAttribute(ServiceSecurity mode,
                                    string serviceCertificateName)
   {
      m_SecurityBehavior = new SecurityBehavior(mode,serviceCertificateName);
   }

   void IServiceBehavior.AddBindingParameters(ServiceDescription description,
                                             ServiceHostBase serviceHostBase,
                                             Collection<ServiceEndpoint> endpoints,
                                             BindingParameterCollection parameters)
   {
     m_SecurityBehavior.AddBindingParameters(description,serviceHostBase,
                                             endpoints,parameters);
   }
   void IServiceBehavior.Validate(ServiceDescription description,
                                  ServiceHostBase serviceHostBase)
   {
      m_SecurityBehavior.UseAspNetProviders = UseAspNetProviders;
      m_SecurityBehavior.ApplicationName = ApplicationName;
      m_SecurityBehavior.ImpersonateAll = ImpersonateAll;
      m_SecurityBehavior.Validate(description,serviceHostBase);
   }
   //Rest of the implementation
}

The SecurityBehavior attribute is a service behavior attribute, so you can apply it directly on the service class. When the AddBindingParameters( ) method of IServiceBehavior is called, the SecurityBehavior attribute enforces the binding configuration that matches the requested scenario. The Validate( ) method of IServiceBehavior is where the SecurityBehavior attribute configures the host. Other than that, all the attribute really does is sequence the overall order of configuration. The actual configuration is accomplished using a helper class called SecurityBehavior. Recall from other examples that it is always best to separate the attribute from its behavior, so you can reuse the behavior elsewhere. The SecurityBehavior attribute constructs an instance of SecurityBehavior, providing it with the scenario (the mode parameter) as well as the certificate name in the matching constructor. SecurityBehavior provides systematic, meticulous setting of all security scenarios using programmatic calls, encapsulating all the explicit steps described previously for each scenario. SecurityBehavior is a service behavior in its own right, and it is designed to even be used standalone, independent of the attribute. Example 10.21, "Implementing SecurityBehavior (partial)" contains a partial listing of SecurityBehavior, demonstrating how it operates.

Example 10.21. Implementing SecurityBehavior (partial)

class SecurityBehavior : IServiceBehavior
{
   ServiceSecurity m_Mode;
   StoreLocation m_StoreLocation;
   StoreName m_StoreName;
   X509FindType m_FindType;
   string m_SubjectName;

   public bool ImpersonateAll
   {get;set;}
   public bool UseAspNetProviders
   {get;set;}
   public string ApplicationName
   {get;set;}

   public SecurityBehavior(ServiceSecurity mode) :
          this(mode,StoreLocation.LocalMachine,X509FindType.FindBySubjectName,null)
   {}
   public SecurityBehavior(ServiceSecurity mode,StoreLocation storeLocation,
                           StoreName storeName,X509FindType findType,
                           string subjectName)
   {...} //Sets the corresponding members

   public void Validate(ServiceDescription description,
                        ServiceHostBase serviceHostBase)
   {
      if(m_SubjectName != null)
      {
         switch(m_Mode)
         {
            case ServiceSecurity.Anonymous:
            case ServiceSecurity.BusinessToBusiness:
            case ServiceSecurity.Internet:
            {
               string subjectName;
               if(m_SubjectName != String.Empty)
               {
                  subjectName = m_SubjectName;
               }
               else
               {
                  subjectName = description.Endpoints[0].Address.Uri.Host;
               }
               serviceHostBase.Credentials.ServiceCertificate.
                SetCertificate(m_StoreLocation,m_StoreName,m_FindType,subjectName);
               break;
            }
         }
      }
      .
      .
      .
   }
   public void AddBindingParameters(ServiceDescription description,
                                    ServiceHostBase serviceHostBase,
                                    Collection<ServiceEndpoint> endpoints,
                                    BindingParameterCollection parameters)
   {
      .
      .
      .
      switch(m_Mode)
      {
         case ServiceSecurity.Intranet:
         {
            ConfigureIntranet(endpoints);
            break;
         }
         case ServiceSecurity.Internet:
         {
            ConfigureInternet(endpoints,UseAspNetProviders);
            break;
         }
         .
         .
         .
      }
   }
   internal static void ConfigureInternet(Collection<ServiceEndpoint> endpoints)
   {
      foreach(ServiceEndpoint endpoint in endpoints)
      {
         Binding binding = endpoint.Binding;
         if(binding is WSHttpBinding)
         {

            WSHttpBinding wsBinding = (WSHttpBinding)binding;
            wsBinding.Security.Mode = SecurityMode.Message;
            wsBinding.Security.Message.ClientCredentialType =
                                                    MessageCredentialType.UserName;
            continue;
         }
         .
         .
         .
         throw new InvalidOperationException(binding.GetType(  ) +
                                   "is unsupported with ServiceSecurity.Internet");
      }
   }
   //Rest of the implementation
}

The constructors of SecurityBehavior store in member variables the construction parameters, such as the security mode and the details of the certificate. The Validate( ) method is a decision tree that configures the host according to the scenario and the provided information, supporting the behavior of the SecurityBehavior attribute. AddBindingParameters( ) calls a dedicated helper method for each scenario to configure the collection of endpoints the host exposes. Each helper method (such as ConfigureInternet( )) iterates over the collection of service endpoints. For each endpoint, it verifies whether the binding used matches the scenario and then configures the binding according to the scenario.

Host-Side Declarative Security

While configuring declarative security via the SecurityBehavior attribute is easy and handy, often it is up to the host to configure security, and the service just focuses on the business logic. In addition, you may be required to host services you do not develop, and those services may not happen to use my declarative security framework. The natural next step is to add declarative security support to the service host class as a set of SetSecurityBehavior( ) extension methods:

public static class SecurityHelper
{
   public static void SetSecurityBehavior(this ServiceHost host,
                                          ServiceSecurity mode,
                                          bool useAspNetProviders,
                                          string applicationName,
                                          bool impersonateAll);

   public static void SetSecurityBehavior(this ServiceHost host,
                                          ServiceSecurity mode,
                                          string serviceCertificateName,
                                          bool useAspNetProviders,
                                          string applicationName,
                                          bool impersonateAll);

   public static void SetSecurityBehavior(this ServiceHost host,
                                          ServiceSecurity mode,
                                          StoreLocation storeLocation,
                                          StoreName storeName,
                                          X509FindType findType,
                                          string serviceCertificateName,
                                          bool useAspNetProviders,
                                          string applicationName,
                                          bool impersonateAll);
   //More members
}

Using declarative security via the host follows the same consistent guidelines as with the SecurityBehavior attribute. For example, here is how to configure the host (and the service) for Internet security with ASP.NET providers:

ServiceHost<MyService> host = new ServiceHost<MyService>(  );
host.SetSecurityBehavior(ServiceSecurity.Internet,
                         "MyServiceCert",true,"MyApplication",false);
host.Open(  );

Example 10.22, "Adding declarative security extensions for the host" shows a partial listing of the declarative security support in the extension methods for the host.

Example 10.22. Adding declarative security extensions for the host

public static class SecurityHelper
{
   public static void SetSecurityBehavior(this ServiceHost host,
                                          ServiceSecurity mode,
                                          StoreLocation storeLocation,
                                          StoreName storeName,
                                          X509FindType findType,
                                          string serviceCertificateName,
                                          bool useAspNetProviders,
                                          string applicationName,
                                          bool impersonateAll)
   {
      if(host.State == CommunicationState.Opened)
      {
         throw new InvalidOperationException("Host is already opened");
      }
      SecurityBehavior securityBehavior = new SecurityBehavior(mode,storeLocation,
                                                               storeName,findType,
                                                          serviceCertificateName);
      securityBehavior.UseAspNetProviders = useAspNetProviders;
      securityBehavior.ApplicationName = applicationName;
      securityBehavior.ImpersonateAll = impersonateAll;

     host.Description.Behaviors.Add(securityBehavior);
   }
   //More members
}

The implementation of SetSecurityBehavior( ) relies on the fact that the SecurityBehavior class supports IServiceBehavior. SetSecurityBehavior( ) initializes an instance of SecurityBehavior with the supplied parameters and then adds it to the collection of behaviors in the service description, as if the service were decorated with the SecurityBehavior attribute.

Client-Side Declarative Security

WCF does not allow attributes to be applied on the proxy class, and while a contract-level attribute is possible, the client may need to provide its credentials and other settings at runtime. The first step in supporting declarative security on the client side is my SecurityHelper static helper class with its set of extension methods for the proxy, defined in Example 10.23, "Adding declarative security extensions for the proxy".

Example 10.23. Adding declarative security extensions for the proxy

public static class SecurityHelper
{
   public static void UnsecuredProxy<T>(this ClientBase<T> proxy)  where T : class;
   public static void AnonymousProxy<T>(this ClientBase<T> proxy)  where T : class;
   public static void SecureProxy<T>(this ClientBase<T> proxy,
                                  string userName,string password) where T : class;
   public static void SecureProxy<T>(this ClientBase<T> proxy,
                    string domain,string userName,string password) where T : class;
   public static void SecureProxy<T>(this ClientBase<T> proxy,string domain,
        string userName,string password,TokenImpersonationLevel impersonationLevel)
                                                                   where T : class;
   public static void SecureProxy<T>(this ClientBase<T> proxy,
                                     string clientCertificateName) where T : class;
   public static void SecureProxy<T>(this ClientBase<T> proxy,
                                   StoreLocation storeLocation,StoreName storeName,
               X509FindType findType,string clientCertificateName) where T : class;
   //More members
}

You can use SecurityHelper to configure a plain proxy according to the desired security scenario and behavior, using the dedicated static extension methods SecurityHelper offers. You can configure the proxy only before opening it. There is no need for any security settings in the client's config file or elsewhere in the client's code.

SecurityHelper is smart, and it will select the correct security behavior based on provided parameters and the method invoked. There is no need to explicitly use the ServiceSecurity enum.

For example, here is how to secure a proxy for the intranet scenario and provide it with the client's Windows credentials:

MyContractClient proxy = new MyContractClient(  );
proxy.SecureProxy("MyDomain","MyUsername","MyPassword");
proxy.MyMethod(  );
proxy.Close(  );

For the Internet scenario, the client only needs to provide the username and the password (remember that the decision as to whether those are Windows or ASP.NET provider credentials is a service-side decision):

MyContractClient proxy = new MyContractClient(  );
proxy.SecureProxy("MyUsername","MyPassword");
proxy.MyMethod(  );
proxy.Close(  );

For the business-to-business scenario, the client can specify a null or an empty string for the client certificate name if it wants to use the certificate in its config file, or it can list the certificate name explicitly:

MyContractClient proxy = new MyContractClient(  );
proxy.SecureProxy("MyClientCert");
proxy.MyMethod(  );
proxy.Close(  );

SecurityHelper will load the certificate from the client's LocalMachine store from the My folder by name. The client can also specify all the information required to find and load the certificate. To keep the design of SecurityHelper simple, when using the BasicHttpBinding in the business-to-business scenario the client must explicitly specify the service certificate location, either in the config file or programmatically.

For an anonymous client, use the AnonymousProxy( ) method:

MyContractClient proxy = new MyContractClient(  );
proxy.AnonymousProxy(  );
proxy.MyMethod(  );
proxy.Close(  );

and for no security at all, use the UnsecuredProxy( ) method:

MyContractClient proxy = new MyContractClient(  );
proxy.UnsecuredProxy(  );
proxy.MyMethod(  );
proxy.Close(  );

Implementing SecurityHelper

Internally, SecurityHelper uses SecurityBehavior to configure the proxy's endpoint and set the credentials, as shown in Example 10.24, "Implementing SecurityHelper (partial)".

Example 10.24. Implementing SecurityHelper (partial)

public static class SecurityHelper
{
   public static void SecureProxy<T>(this ClientBase<T> proxy,
                                   string userName,string password) where T : class
   {
      if(proxy.State == CommunicationState.Opened)
      {
         throw new InvalidOperationException("Proxy channel is already opened");
      }
      Collection<ServiceEndpoint> endpoints = new Collection<ServiceEndpoint>(  );
      endpoints.Add(proxy.Endpoint);

      SecurityBehavior.ConfigureInternet(endpoints,true);

      proxy.ClientCredentials.UserName.UserName = userName;
      proxy.ClientCredentials.UserName.Password = password;
      proxy.ClientCredentials.ServiceCertificate.Authentication.
               CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
   }
   //Rest of the implementation
}

The SecureClientBase<T> class

The advantage of using the SecurityHelper extensions is that they can operate on any proxy-even a proxy the client developer is not responsible for creating. The disadvantage is that it is an extra step the client has to take. If you are responsible for generating the proxy, you can take advantage of my SecureClientBase<T> class, defined in Example 10.25, "The SecureClientBase<T> class".

Example 10.25. The SecureClientBase<T> class

public abstract class SecureClientBase<T> : ClientBase<T> where T : class
{
   //These constructors target the default endpoint
   protected SecureClientBase(  );
   protected SecureClientBase(ServiceSecurity mode);
   protected SecureClientBase(string userName,string password);
   protected SecureClientBase(string domain,string userName,string password,
                              TokenImpersonationLevel impersonationLevel);
   protected SecureClientBase(string domain,string userName,string password);
   protected SecureClientBase(string clientCertificateName);
   protected SecureClientBase(StoreLocation storeLocation,StoreName storeName,
                              X509FindType findType,string clientCertificateName);
   //More constructors for other types of endpoints
}

SecureClientBase<T> derives from the conventional ClientBase<T> and adds declarative security support. You need to derive your proxy from SecureClientBase<T> instead of ClientBase<T>, provide constructors that match your security scenario, and call the base constructors of SecureClientBase<T> with the supplied credentials and endpoint information:

class MyContractClient : SecureClientBase<IMyContract>,IMyContract
{
   public MyContractClient(ServiceSecurity mode) : base(mode)
   {}
   public MyContractClient(string userName,string password) :
                                                           base(userName,password)
   {}

   /* More constructors */

   public void MyMethod(  )
   {
      Channel.MyMethod(  );
   }
}

Using the derived proxy is straightforward. For example, for the Internet scenario:

MyContractClient proxy = new MyContractClient("MyUsername","MyPassword");
proxy.MyMethod(  );
proxy.Close(  );

or for the Anonymous scenario:

MyContractClient proxy = new MyContractClient(ServiceSecurity.Anonymous);
proxy.MyMethod(  );
proxy.Close(  );

The implementation of SecureClientBase<T> simply uses the extensions of SecurityHelper (as shown in Example 10.26, "Implementing SecureClientBase<T> (partial)"), so SecureClientBase<T> follows the same behaviors regarding, for example, the client certificate.

Example 10.26. Implementing SecureClientBase<T> (partial)

public class SecureClientBase<T> : ClientBase<T> where T : class
{
   protected SecureClientBase(ServiceSecurity mode)
   {
      switch(mode)
      {
         case ServiceSecurity.None:
         {
            this.UnsecuredProxy(  );
            break;
         }
         case ServiceSecurity.Anonymous:
         {
            this.AnonymousProxy(  );
            break;
         }
         ...
      }
   }
   protected SecureClientBase(string userName,string password)
   {
      this.SecureProxy(userName,password);
   }
   //More constructors
}

Secure channel factory

If you are not using a proxy at all, SecurityHelper and SecureClientBase<T> will be of little use to you. For that case, I added to SecurityHelper a set of extension methods to the ChannelFactory<T> class, defined in Example 10.27, "Adding declarative security extensions for ChannelFactory<T>".

Example 10.27. Adding declarative security extensions for ChannelFactory<T>

public static class SecurityHelper
{
   public static void SetSecurityMode<T>(this ChannelFactory<T> factory,
                                         ServiceSecurity mode);
   public static void SetCredentials<T>(this ChannelFactory<T> factory,
                                        string userName,string password);
   public static void SetCredentials<T>(this ChannelFactory<T> factory,
                                    string domain,string userName,string password);
   public static void SetCredentials<T>(this ChannelFactory<T> factory,
                                        string clientCertificateName);
   //More members
}

You need to call the SetSecurityMode( ) method or one of the SetCredentials( ) methods that fits your target scenario before opening the channel. For example, with a proxy to an Internet security-based service:

ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>( . . . );
factory.SetCredentials("MyUsername","MyPassword");

IMyContract proxy = factory.CreateChannel(  );

using(proxy as IDisposable)
{
   proxy.MyMethod(  );
}

Implementing the ChannelFactory<T> extensions was very similar to implementing the extensions for ClientBase<T>, so I have omitted that code.

Duplex clients and declarative security

I also provide the SecureDuplexClientBase<T,C> class (similar to SecureClientBase<T>), which is defined in Example 10.28, "The SecureDuplexClientBase<T,C> class".

Example 10.28. The SecureDuplexClientBase<T,C> class

public abstract class SecureDuplexClientBase<T,C> : DuplexClientBase<T,C>
                                                                    where T : class
{
   protected SecureDuplexClientBase(C callback);
   protected SecureDuplexClientBase(ServiceSecurity mode,C callback);
   protected SecureDuplexClientBase(string userName,string password,C callback);
   protected SecureDuplexClientBase(string domain,string userName,string password,
                           TokenImpersonationLevel impersonationLevel,C callback);
   protected SecureDuplexClientBase(string domain,string userName,string password,
                                                                      C callback);
   protected SecureDuplexClientBase(string clientCertificateName,C callback);
   protected SecureDuplexClientBase(StoreLocation storeLocation,
                                    StoreName storeName,X509FindType findType,
                                    string clientCertificateName,C callback);

  /* More constructors with InstanceContext<C> and constructors that
     target the configured endpoint and a programmatic endpoint */
}

SecureDuplexClientBase<T,C> derives from my type-safe DuplexClientBase<T,C> class, presented in Chapter 5, Operations, and it adds declarative scenario-based security support. As when using the DuplexClientBase<T,C> class, you need to derive your proxy class from it and take advantage of either the callback parameter or the type-safe context InstanceContext<C>. For example, given this service contract and callback contract definition:

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
   [OperationContract]
   void MyMethod(  );
}
interface IMyContractCallback
{
   [OperationContract]
   void OnCallback(  );
}

your derived proxy class will look like this:

class MyContractClient :
                SecureDuplexClientBase<IMyContract,IMyContractCallback>,IMyContract
{
   public MyContractClient(IMyContractCallback callback) : base(callback)
   {}
   public MyContractClient(ServiceSecurity mode,IMyContractCallback callback) :
                                                                base(mode,callback)
   {}
   /* More constructors */

   public void MyMethod(  )
   {
      Channel.MyMethod(  );
   }
}

When using SecureDuplexClientBase<T,C>, provide the security scenario or credentials, the callback object, and the endpoint information. For example, when targeting the anonymous scenario:

class MyClient : IMyContractCallback
{...}

IMyContractCallback callback = new MyClient(  );

MyContractClient proxy = new MyContractClient(ServiceSecurity.Anonymous,callback);
proxy.MyMethod(  );

proxy.Close(  );

The implementation of SecureDuplexClientBase<T,C> is almost identical to that of SecureClientBase<T>, with the main difference being a different base class. Note that there was no point in defining declarative extensions for DuplexClientBase<T>, since you should not use it in the first place due to its lack of type safety.

Extensions for the duplex factory

When you're not using a SecureDuplexClientBase<T,C>-derived proxy to set up the bidirectional communication, you can use my declarative extensions for the DuplexChannelFactory<T,C> channel factory, defined in Example 10.29, "Adding declarative security extensions for DuplexChannelFactory<T,C>".

Example 10.29. Adding declarative security extensions for DuplexChannelFactory<T,C>

public static class SecurityHelper
{
   public static void SetSecurityMode<T,C>(this DuplexChannelFactory<T,C> factory,
                                           ServiceSecurity mode);
   public static void SetCredentials<T,C>(this DuplexChannelFactory<T,C> factory,
                                          string userName,string password);
   public static void SetCredentials<T,C>(this DuplexChannelFactory<T,C> factory,
                                   string domain,string userName,string password);
   public static void SetCredentials<T,C>(this DuplexChannelFactory<T,C> factory,
                                          string clientCertificateName);
   //More members
}

You need to call the SetSecurityMode( ) method or one of the SetCredentials( ) methods that fits your target scenario before opening the channel. For example, when targeting the Internet scenario:

class MyClient : IMyContractCallback
{...}

IMyContractCallback callback = new MyClient(  );

DuplexChannelFactory<IMyContract,IMyContractCallback> factory =
            new DuplexChannelFactory<IMyContract,IMyContractCallback>(callback,");
factory.SetCredentials("MyUsername","MyPassword");

IMyContract proxy = factory.CreateChannel(  );
using(proxy as IDisposable)
{
   proxy.MyMethod(  );
}

Implementing the extensions for DuplexChannelFactory<T,C> was very similar to implementing those for ChannelFactory<T>.

I will end this chapter by presenting a useful feature WCF supports called security audits. As its name implies, a security audit is a logbook of the security-related events in your services. WCF can log authentication and authorization attempts, their times and locations, and the calling clients' identities. The class ServiceSecurityAuditBehavior governs auditing; it is listed in Example 10.30, "The ServiceSecurityAuditBehavior class" along with its supporting enumerations.

Example 10.30. The ServiceSecurityAuditBehavior class

public enum AuditLogLocation
{
   Default, //Decided by the operating system
   Application,
   Security
}
public enum AuditLevel
{
   None,
   Success,
   Failure,
   SuccessOrFailure
}
public sealed class ServiceSecurityAuditBehavior : IServiceBehavior
{
   public AuditLogLocation AuditLogLocation
   {get;set;}
   public AuditLevel MessageAuthenticationAuditLevel
   {get;set;}
   public AuditLevel ServiceAuthorizationAuditLevel
   {get;set;}
   //More members
}

ServiceSecurityAuditBehavior is a service behavior. The AuditLogLocation property specifies where to store the log entries: in the application log or in the security log, both of which are in the event log on the host computer. The MessageAuthenticationAuditLevel property governs the authentication audit verbosity. Its default value is AuditLevel.None. For performance's sake, you may want to audit only failures. For diagnostic purposes, you can also audit successful authentications. Similarly, you use the ServiceAuthorizationAuditLevel property to control authorization audit verbosity. It is also disabled by default.

Configuring Security Audits

The typical way of enabling a security audit is in the host config file, by adding a custom behavior section and referencing it in the service declaration, as shown in Example 10.31, "Configuring a security audit administratively".

Example 10.31. Configuring a security audit administratively

<system.serviceModel>
   <services>
      <service name = "MyService" behaviorConfiguration = "MySecurityAudit">
         ...
      </service>
   </services>
   <behaviors>
      <serviceBehaviors>
         <behavior name = "MySecurityAudit">
            <serviceSecurityAudit
               auditLogLocation = "Default"
               serviceAuthorizationAuditLevel  = "SuccessOrFailure"
               messageAuthenticationAuditLevel = "SuccessOrFailure"
            />
         </behavior>
      </serviceBehaviors>
   </behaviors>
</system.serviceModel>

You can also configure security auditing programmatically, by adding the behavior to the host at runtime before opening it. As when adding other behaviors programmatically, you can check that the host does not already have an audit behavior to avoid overriding the config file, as shown in Example 10.32, "Enabling a security audit programmatically".

Example 10.32. Enabling a security audit programmatically

ServiceHost host = new ServiceHost(typeof(MyService));

ServiceSecurityAuditBehavior securityAudit =
                   host.Description.Behaviors.Find<ServiceSecurityAuditBehavior>(  );
if(securityAudit == null)
{
   securityAudit = new ServiceSecurityAuditBehavior(  );

   securityAudit.MessageAuthenticationAuditLevel = AuditLevel.SuccessOrFailure;
   securityAudit.ServiceAuthorizationAuditLevel = AuditLevel.SuccessOrFailure;
   host.Description.Behaviors.Add(securityAudit);
}
host.Open(  );

You can streamline the code in Example 10.32, "Enabling a security audit programmatically" by adding the SecurityAuditEnabled Boolean property to ServiceHost<T>:

public class ServiceHost<T> : ServiceHost
{
   public bool SecurityAuditEnabled
   {get;set;}
   //More members
}

Using ServiceHost<T>, Example 10.32, "Enabling a security audit programmatically" is reduced to:

ServiceHost<MyService> host = new ServiceHost<MyService>(  );
host.SecurityAuditEnabled = true;
host.Open(  );

Example 10.33, "Implementing the SecurityAuditEnabled property" shows the implementation of the SecurityAuditEnabled property.

Example 10.33. Implementing the SecurityAuditEnabled property

public class ServiceHost<T> : ServiceHost
{
   public bool SecurityAuditEnabled
   {
      get
      {
         ServiceSecurityAuditBehavior securityAudit =
                        Description.Behaviors.Find<ServiceSecurityAuditBehavior>(  );
         if(securityAudit != null)
         {
            return securityAudit.MessageAuthenticationAuditLevel ==
                                                        AuditLevel.SuccessOrFailure
                   &
                   securityAudit.ServiceAuthorizationAuditLevel ==
                                                       AuditLevel.SuccessOrFailure;
         }
         else
         {
            return false;
         }
      }
      set
      {
         if(State == CommunicationState.Opened)
         {
            throw new InvalidOperationException("Host is already opened");
         }
         ServiceSecurityAuditBehavior securityAudit =
                        Description.Behaviors.Find<ServiceSecurityAuditBehavior>(  );
         if(securityAudit == null & value == true)
         {
            securityAudit = new ServiceSecurityAuditBehavior(  );
            securityAudit.MessageAuthenticationAuditLevel =
                                                       AuditLevel.SuccessOrFailure;
            securityAudit.ServiceAuthorizationAuditLevel =
                                                       AuditLevel.SuccessOrFailure;
            Description.Behaviors.Add(securityAudit);
         }
      }
   }
   //More members
}

In the get accessor, the SecurityAuditEnabled property accesses the description of the service and looks for an instance of ServiceSecurityAuditBehavior. If one is found, and if both the authentication and the authorization audits are set to AuditLevel.SuccessOrFailure, SecurityAuditEnabled returns true; otherwise, it returns false. In the set accessor, the property enables the security audit only if the description does not contain a previous value (because the config file does not contain the audit behavior). If no prior behavior is found, SecurityAuditEnabled sets both the authentication and authorization audits to AuditLevel.SuccessOrFailure.

Declarative Security Auditing

You can also write an attribute that surfaces the security audit options at the service level. I chose to add that support in the form of a single Boolean property of the SecurityBehavior attribute called SecurityAuditEnabled:

[AttributeUsage(AttributeTargets.Class)]
public class SecurityBehaviorAttribute : Attribute,IServiceBehavior
{
   public bool SecurityAuditEnabled
   {get;set;}
   //More members
}

The default of SecurityAuditEnabled is false (i.e., no security audit). Using this property complements the rest of the declarative security model. For example:

[SecurityBehavior(ServiceSecurity.Internet,UseAspNetProviders = true,
                  SecurityAuditEnabled = true)]
class MyService : IMyContract
{...}

Example 10.34, "Implementing a declarative security audit" shows how that support was added to the SecurityBehavior attribute.

Example 10.34. Implementing a declarative security audit

[AttributeUsage(AttributeTargets.Class)]
public class SecurityBehaviorAttribute : Attribute,IServiceBehavior
{
   public bool SecurityAuditEnabled
   {get;set;}

   void IServiceBehavior.Validate(ServiceDescription description,
                                  ServiceHostBase serviceHostBase)
   {
      if(SecurityAuditEnabled)
      {
         ServiceSecurityAuditBehavior securityAudit = serviceHostBase.Description.
                                    Behaviors.Find<ServiceSecurityAuditBehavior>(  );
         if(securityAudit == null)
         {
            securityAudit = new ServiceSecurityAuditBehavior(  );
            securityAudit.MessageAuthenticationAuditLevel =
                                                       AuditLevel.SuccessOrFailure;
            securityAudit.ServiceAuthorizationAuditLevel =
                                                       AuditLevel.SuccessOrFailure;
            serviceHostBase.Description.Behaviors.Add(securityAudit);
         }
         //Rest same as Example 10-20
      }
   }
   //Rest of the implementation
}

The Validate( ) method of IServiceBehavior enables auditing using the same verbosity level as ServiceHost<T>, again avoiding overriding the config file.


[8] I first published an earlier version of Credentials Manager in my article "Manage Custom Security Credentials the Smart (Client) Way" (CoDe Magazine, November 2005).

Show:
© 2014 Microsoft