
March 2, 2006
This is part two of a two-part series. Click here to read part one, "Connecting Web services with the System.ServiceModel."
In this article I will show you how you can implement security on a WCF service. There are many options and extensibility points for implementing security in WCF. You can also use specific
products, such as the Windows 2003 Server Authorization Manager, together with WCF to implement the authorization requirements of a solution. Out of the box, WCF supports Windows
credentials, Username Tokens and X.509 Digital Certificates as security credentials.
I will touch on a few security scenarios that are most commonly used today. These will also offer the best option in security interoperability today. Then, I will show a sample where I
will use the more advanced WS-* security standards (WS-Trust and WS-SecureConversation) and show how easy it is to use those standards (in case it is needed in the future) with just the
flick of a configuration switch.
Username Tokens via Transport-based Security
A Username token is just like using basic HTTP authentication, except the token is within the SOAP message headers. It is one of the simplest and easiest security token-based solutions to
implement today, and it is one of the most common as well. Many solutions today authenticate the username credentials either with a user directory or with a user database. It has proven to
be a flexible approach that scales well in the enterprise today.
However, one of the banes of a username and password combination is that users will tend to choose a trivial password or publish it somewhere without even knowing it. Because the general
keyspace of a password in a normal human's brain is relatively small (people generally cannot remember long and complex passwords), it is just not safe to use keys based on passwords. This
recommendation is further heightened if you think about the threats of the dreaded offline brute force and dictionary attacks.
The WS-* toolkits of yesterday and today never did prescribed against use of that nor did it disallow it. The security team of WCF got it right by disallowing the use of username
credentials to sign or encrypt the message payload. However, to provision for this widely popular form of user authentication, WCF allows the use of a transport layer security
(HTTPS/SSL) to secure the username token.
Since we are on the subject of HTTPS, I would be hosting this secured service via IIS as explained above.
Default: Windows Credentials
WCF uses Windows Credentials by default. What this means is that if you do not specify anything explicitly, WCF will authenticate against your Windows user store.
The technical deployment details (Protocols, Encodings and Transports) for WCF are specified in the <bindings> section of the configuration file.
<system.serviceModel>
<services>
<service type="MyFirstSecuredWCFService, WebApp">
<endpoint address="" contract="IMyFirstSecuredWCFService, WebApp"
binding="basicHttpBinding" bindingConfiguration="SWMExtendedBasicHttpBinding" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding configurationName="SWMExtendedBasicHttpBinding">
<!-- UsernameToken over Transport Security -->
<security mode="TransportWithMessageCredential">
<message clientCredentialType ="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
Table 10
There are three ways of configuring a binding in WCF. You can define a standard existing binding that ships out-of-the-box. You can also extend that standard existing binding.
Lastly, the most extensible approach is for you to define your own custom bindings.
What I did in Table 10 was extend the <basicHttpBinding>, which is also known as the Basic Profile HTTP Binding. Notice how I specify the loading of the specific service and
contract type in the <service> element configuration and how it ties to the interface and implementation code in Table 2 and 3. I extended the <basicHttpBinding> element by
setting the security mode to "TransportWithMessageCredential" and then specifying the clientCredentialType attribute of the message mode to be that of "Username."
Translated, this means: "I am sending a username credential type in the message's headers and that I would like to protect it via a transport-level security protocol." I have to configure
IIS and the virtual directory security where this service is hosted to have secure communications (HTTPS/SSL).
If I do not do this, WCF will barf up an exception -- something to the effect of complaining that the Directory Security\Secure Communications in IIS is not set up properly.
[Author note]: For HTTPS communication between the two endpoints, the appropriate SSL Certificate is to be properly installed in the Digital Certificate store and the Directory
Security\Secure Communications in IIS is properly set up as well.
On the client side, I have to add these two lines into my code I have in Table 8 that adds the username and password credentials to the client-side channel factory (see Table 11).
Sub Main()
Console.WriteLine("Press enter when the Credit Card Service is running ...")
Console.ReadLine()
Dim pxy As New MyFirstSecuredWCFServiceProxy
pxy.ChannelFactory.Credentials.UserNamePassword.UserName = "Softwaremaker"
pxy.ChannelFactory.Credentials.UserNamePassword.Password = "SomePassword"
Dim cc As New CreditCard
cc.AccountName = "William Tay"
...
End Sub
End Module
Table 11
The result will be a username token embedded in the <headers> of the SOAP message (See Table 12). Even though you can see it in clear text once it arrives at the service, you can be
assured that the message is encrypted on its way there since we are using HTTPS.
<s:Envelope xmlns:s="...">
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="...">
<u:Timestamp u:Id="uuid-c16f2bd1-...">
<u:Created>2005-10-26T23:56:27.109Z</u:Created>
<u:Expires>2005-10-27T00:11:27.109Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-060c77ce-...">
<o:Username>Softwaremaker</o:Username>
<o:Password o:Type="...">SomePassword</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<SubmitCreditCard xmlns="...">
...
</SubmitCreditCard>
</s:Body>
</s:Envelope>
Table 12
.NET 2.0 Membership Provider: Custom Username and password
We have just seen how we can authenticate a user via a username credential using WCF against the Windows user store. However, a more common scenario would be to authenticate against a
custom user store such as a SQL Server database.
The solution lies in the new built-in support for membership (user name/password credential storage) and role management services provided by the .NET Framework 2.0 and Visual Studio 2005
(You can read more about how to implement a Membership Provider in .NET 2.0 here).
Table 13 shows a short snippet of my example of the membership provider.
Public Class SWMMembershipProvider : Inherits MembershipProvider
...
Public Overrides Function ValidateUser(ByVal username As String, ByVal password As String)
As Boolean
'Look up a user database with ADO.NET 2.0 for example ...
'and retrieve a hashed password based on the username passed in
Dim _retrievedPwdDigest as String
_retrievedPwdDigest = SomePasswordDigestRetrievedFromTheUserDB(username)
If _retrievedPwdDigest = HashDigest(password) Then Return True
End Function
...
End Class
Table 13
Once we have out own membership provider set up, all we need to do is to hook it up in our configuration file (See Table 14). There are a couple of settings to take note. Notably, we have
to set the membershipProviderName attribute of the <serviceCredentials> \ <userNamePassword> element under the <behavior> section to the provider type (See Table
13) / name that I have set in the <membership> \ <providers> section.
<system.serviceModel>
<services>
<service
type="MyFirstSecuredWCFService, WebApp"
behaviorConfiguration="SWMMembershipProviderOverHttps">
<endpoint
address=""
contract="IMyFirstSecuredWCFService, WebApp"
binding="basicHttpBinding" bindingConfiguration="SWMExtendedBasicHttpBinding" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding configurationName="SWMExtendedBasicHttpBinding">
<!-- UsernameToken over Transport Security -->
<security mode="TransportWithMessageCredential">
<message clientCredentialType ="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<behavior
configurationName="SWMMembershipProviderOverHttps"
returnUnknownExceptionsAsFaults="true">
<serviceCredentials>
<!-- UsernameToken over Transport Security -->
<userNamePassword membershipProviderName ="SWMMembershipProvider"/>
</serviceCredentials>
</behavior>
</behaviors>
</system.serviceModel>
<system.web>
<membership>
<providers>
<add name="SWMMembershipProvider" type="SWMMembershipProvider,WebApp" />
</providers>
</membership>
</system.web>
Table 14
The wire message generated from the above will be similar to the one shown in Table 12. The only difference is that instead of authenticating against your Windows user store, this message
authenticates against a custom username and password store as defined by your .NET 2.0 MembershipProvider class in Table 13. As you can imagine, one of the key benefits is the ability to
change and swap membership providers in the configuration file as needed.
Basic Security Profile: Mutual Certificates via Message-based Security
While authenticating a message exchange via username and password credentials via a transport-level security is useful, it has its limitations. It is great for point-to-point solutions
and where HTTP is the only mode of transport used -- but we would need a message-level security protocol for an end-to-end scenario where intermediaries come into play and where a multitude
of different transports must be dealt with in the entire message path. Solutions like that will become more common in the entire collaboration value chain of the enterprise in the future, as
business processes and technical specifications and protocols such as WS-Addressing become more mature and accepted.
We can just as easily define a message-based security authentication mechanism by extending the <basicHttpBinding> in a similar way as I have shown above.
<services>
<service type="MyFirstSecuredWCFService, ConsoleApp"
behaviorConfiguration="BasicSecurityProfileMutualCertificateBehavior">
<endpoint address="http://localhost:2088/MyFirstSecuredWCFService"
contract="IMyFirstSecuredWCFService, ConsoleApp"
binding="basicHttpBinding"
bindingConfiguration="SWMExtendedBasicHttpBinding" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding configurationName="SWMExtendedBasicHttpBinding">
<!-- BasicSecurityProfile MutualCertificate -->
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<behavior configurationName="BasicSecurityProfileMutualCertificateBehavior"
returnUnknownExceptionsAsFaults="true">
<serviceCredentials>
<serviceCertificate findValue="Bob"
storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</behaviors>
Table 15
Table 15 shows the configuration file of a WCF service that defines a message-based authentication mode with the use of mutual X.509 Digital Certificates.
The serviceCredentials behavior allows one to define a service certificate. A service certificate is used by a client to authenticate the service and provide message protection.
This configuration references the "Bob" certificate, which I have installed on my machine.
Table 16 shows the corresponding <behavior> element section configuration on the client.
<behaviors>
<behavior configurationName="BasicSecurityProfileMutualCertificateBehavior"
returnUnknownExceptionsAsFaults="true" >
<clientCredentials>
<clientCertificate findValue="Alice" storeLocation="CurrentUser"
storeName="My" x509FindType="FindBySubjectName" />
<serviceCertificate findValue="Bob" storeLocation="LocalMachine"
storeName="My" x509FindType="FindBySubjectName" />
</clientCredentials>
</behavior>
</behaviors>
Table 16
The clientCredentials behavior allows one to define a certificate to present to the WCF service. A certificate is used by a service to authenticate the client and provide message
protection back to the client.
Once all this is set up, the message exchange from the client to the service will be digitally signed first by the client's X.509 Digital Certificate (private-key) and then encrypted for
the WCF service with the service's certificate (public-key). The service will be able to decrypt the message with its own private key and then validate that the message from the client is
not tampered with via the client's public key installed in its Trusted People certificate store. The message response from the service to the client is then correspondingly signed
first by the service and then encrypted for the client only by the public key found on the client's X.509 Digital Certificate.
WCF also allows the use of a supporting token approach whereby a username token is used as the authentication mechanism. However, this username credential and its claims and the entire
message is (and should be) secured with a signed token such as a X.509 Digital Certificate for the best security approach forward.
This mode of secure message exchange in WCF is great for multi-hop, cross-machines, cross-boundaries solution scenarios. In distributed enterprise solutions as such, where intermediaries
are involved, trust is not point-to-point; it is end-to-end. Trust is based on the credentials supplied in the message, not the location of a node on the message path. If everything were
point-to-point, there would be no need for WS-Security and its related framework and we can just make do with HTTPS / SSL.
Advanced WS-* Security
We have looked at some of the more basic security profile for both transports and message-based security mechanisms. Another newer family of WS-Security specifications is still going
through its standard ratification process. These include the WS-Trust and WS-SecureConversation specifications.
These newer and more advanced security protocols are included in the standard bindings that ships out-of-the-box with WCF. To use it, we just simply configure the <binding> element
of the <service> section to "wsHttpBinding" as shown in Table 17.
<services>
<service type="MyFirstSecuredWCFService, ConsoleApp">
<endpoint address=" ... "
contract="IMyFirstSecuredWCFService, ConsoleApp"
binding="wsHttpBinding" />
</service>
</services>
Table 17
When this binding is being used, a number of bootstrapping calls will be made to exchange for a secure symmetric security token. This will utilize some of the specifications of WS-Trust
and WS-SecureConversation such as the <RequestSecurityToken> (RST), <RequestSecurityTokenResponse> (RSTR), <RequestType>, <SecurityContextToken> (SCT),
<DerivedKeyToken>, <RequestedProofToken>, etc., which you will see in the secured message on the wire.
While some have argued against the cost of all the preparation work just to exchange a context token, this framework is great for performance when it comes to multiple message exchanges
in the same context. This happens very frequently in some of the more advanced WS-* specifications such as WS-ReliableMessaging and WS-Transactions, in which these advanced composable
WS-Security specifications are being built and extended upon. In cases like that, where security and performance is critical over multiple exchanges, the cost of making these bootstrapping
calls is very effectively amortized over many Web service calls, and the performance benefits really does overwhelm the initial setup cost.
It is not just another WS-* toolkit
While I have only scratched the surface of the security features of WCF, I believed I have shown some of the more common methods used on the field today. I expect username and password
credential authentication and X.509 Digital Certificates for cross-machine mutual authentication to be used for some time as the more common interoperable ways that are widely accepted and
used.
Once the advanced WS-* security features (WS-Trust, WS-SecureConversation) reached a certain maturity and widespread adoption, it should provide another very efficient and effective way
of message security and authentication between a multitude of networked parties that spans across different federated identity systems.
From the above examples I have shown, you will be able to see how WCF is designed to enable it to achieve its fundamental objectives I have described at the top of this article. It is by
establishing this clean separation in the programming model -- between the address and the binding on one hand and the contract on the other -- that WCF makes code indifferent to the network.
This allows code to be plugged and swapped into any network without the need for a modify code, compile, build, test, deploy cycle.
I have shown how we did not have to touch any core business compiled code here and adapt that to the changing technical environments with just a few configuration tweaks. It gives me the
ability to adapt to the ever-changing security environments that happens with the dynamic change reviews of security policies, for example.
The same principle applies if we change transport mechanisms as well to suite our business requirements as it scales. The benefits of WCF's architecture and its design objectives are
tremendous. It offers huge flexibility and options in building an enduring service-oriented system that must transcend time and change in the fluid environments of today and tomorrow.
Authors
 |
William Tay is a Senior Consultant/Enterprise Software Architect with NCS Pte. Ltd., based in Singapore. He is a Microsoft Most Valuable Professional (MVP) for Solutions Architect. You can read about his views on software architectures, XML, Web services and other technical ramblings via his technical blog at http://www.softwaremaker.net/blog.
|
|