The official release of Microsoft's Web Services Enhancements (WSE) toolkit promises to help developers deal with at least some of the pain and suffering accompanying the emerging Web services' standards.
Updated to support the OASIS WS-Security specification and a promising WS-Policy specification, developers will be able to build standards-compliant Web services in less time and with less code.
If you've been trying to keep up with the flurry of emerging, re-emerging, and otherwise evolving standards for Web services, your head is probably spinning. It is a difficult task to keep track of which standard is the favorite for standardization, which standards body has taken on what standard, and what toolkits have stepped up to support them. It would be great if we could just drag, drop, point and click our way to a successful enterprise Web services implementation without reading another boring XML specification. For this to become a reality, we need tools, good ones, and we need them all to play nice across platforms so we can "not care" about the underlying XML.
If you thought I was going to say that WSE 2.0 solves all of this, dream on. However, it does bring us one step closer to a mouse-driven paradise. Developers still have a responsibility to understand how emerging Web services standards such as WS-Security, WS-Trust, WS-SecureConversation, WS-Policy and so on (WS*) can be applied to their business workflows, but with WSE 2.0 they have a tool that helps them implement .NET Framework solutions productively.
Developers can quickly generate policies to enforce the security requirements of a Web service, without writing a line of code. This is one of the most exciting new features of WSE 2.0.
In this article, I'll provide an overview of the key features that WSE 2.0 brings to the table, focusing on the applicability of WS-Security and WS-Policy standards as they sit today. I'll take you through building a Web service solution that requires a layered security model, writing as little code as possible, leveraging the WSE 2.0 VS.NET plug-in and its support for WS-SecurityPolicy to build the solution. I'll show you where I had to break down and write some code, wielding the extensibility of the WSE 2.0 pipeline to overcome some of the issues not yet addressed through the tool.
Applying Standards Gets Easier
The WSE 2.0 tool plugs in to the Visual Studio IDE just like WSE 1.0, through a set of tabbed dialog interfaces broken down by function. The new release continues to provide support for routing configuration, custom filters, and diagnostics. It also adds new support for publishing policy documents describing service requirements (see Figure 1) based on the WS-Policy specification, configuring support for signing and encrypting messages (see Figure 2) consistent with OASIS WS-Security, and configuring token issuance services based on WS-Trust and WS-SecureConversation standards.
Modifications made through these interfaces update the appropriate application configuration files (web.config or app.config) to enable features of the WSE run time, but this new tool also automatically generates policy cache files compliant with the WS-Policy specification. Developers can quickly generate policies to enforce the security requirements of a Web service, without writing a single line of code. This is indeed one of the most exciting new features of WSE 2.0).
The WSE 2.0 configuration tool does not replace a fundamental understanding of Web services standards, but it certainly helps. For example, I can point and click my way to a basic WS-SecurityPolicy that requires message signing and encryption; this generates compliant WS-Policy XML that can be shared with clients. Some functionality is not exposed through the tool interface, yet is supported by the Microsoft.Web.Services object model. For example, WS-Policy provides XML grammar for signing multiple parts of a message, and with some skillful hand-editing of the policy file, the run time happily supports this policy (see Signing Multiple Message Parts later in this article). In cases where specification elements are not supported by WSE 2.0, or its built-in run-time filters, you can create your own components to plug-in to the message processing pipeline.
WS-Policy Driven Architecture
The WS-Policy specification describes how Web services can publish requirements and validate messages against those requirements. This goes beyond Web Services Description Language (WSDL) that describes only wire-format. A policy might explicitly reject messages based on content or make certain message parts optional. WS-Policy provides a general XML grammar for describing policy assertions. Other specifications, such as WS-PolicyAssertions and WS-SecurityPolicy, provide specific applications of this grammar for their domains. WSE 2.0 provides rich support for WS-SecurityPolicy as will be demonstrated in this article.
WSE 2.0 automatically generates XML policy files that can contain WS-Policy descriptions for one or more Web service endpoints. This policy XML is used by the WSE run time to validate incoming messages (for example, to ensure that the message is properly signed), and to apply policies to outgoing messages (for example, to encrypt responses). XML policy is also used to publish policy information to calling clients. When WSE 2.0 is enabled for a client application, the run time can apply policy to outgoing messages, and validate messages returning from a Web service invocation.
This is all automatically handled by a set of pipeline filters included in the Microsoft.Web.Services.Policy namespace. PolicyVerificationInput and PolicyEnforcementOutput filters ingest policy XML to verify and enforce policy for incoming and outgoing messages, which means that we write less code. So, although the industry has yet to formally include WS-Policy within a standards organization, the application of WS-Policy at the tool level makes it possible for tools like WSE to automate policy-driven behavior.
WS-PolicyAttachments is a related WS-Policy specification for that describes how policies can be bound to WSDL or published through a UDDI interface. This part of the WS-Policy specification is not supported by tools today, so supplying Web service clients with a policy document is currently a manual process.
Support for OASIS WS-Security
WSE 1.0 supported a few core facets of the initial WS-Security specification; we were able to sign and encrypt messages with username token or digital signatures. Now that the WS-Security specification has stabilized and OASIS (Organization for the Advancement of Structured Information Standards) has it under its wing, we need an updated tool to support additional token formats, secure conversations, and to expose policy bindings as specified in the WS-SecurityPolicy specification.
One of the primary goals of the WS-Security specification is to provide an open specification that can support cross-platform security tokens and encryption standards. WSE 2.0 still supports username and X.509 tokens, but also adds support for Kerberos, security contexts compliant with WS-SecureConversation and WS-Trust, and extensibility to support XrML and SAML for single sign-on solutions.
I will show you how to apply several features of the specification for business scenarios that require a layered approach to Web services security. By the end of this article, you'll know how to apply WSE 2.0 to achieve the following results in a single solution:
- Identify application users for audit trail and authorization
- Control message privacy with encryption
- Verify message integrity by ensuring a trusted source
- Prevent message replay attacks
I'll also show you how much you can automate with WS-Policy support and where you have to write some code to fill in the missing pieces.
Identifying Application Users: Who Is Using the Application?
SOAP messages are generated by client applications but the initiation of the request usually stems from an individual user of that client application or partnering organization. Consequently, exchanges between applications involve the authentication of user credentials. Consider a bank teller client application that invokes Web services to access account information. The teller logs in to the client application and the application passes those credentials to identify the user to the Web services layer. Although the calling application may also require authorization to invoke the Web service, the application user is typically also identified for audit trail purposes and to authorize access to specific service features.
With two lines of code on the client, a single authentication method on the server, and some skillful pointing and clicking to configure policy and security, you have user authentication in place.
The code sample for this article simulates this situation through a bank teller client application (TellerApplication) and a set of Web services that interact with accounts (TellerServices). Throughout this article, I'll build a secure infrastructure around the Web services layer, beginning with the provision of username and password credentials from the client application user.
Authenticating Users
I've mentioned writing as little code as possible, but to authenticate against a custom application credential store, you do have to create a simple class to handle authorization. If credentials are provided with an incoming message, WSE 2.0 looks for a class that derives from UsernameTokenManager, part of the Microsoft.Web.Services.Security.Tokens namespace. The default implementation of this class invokes the Win32 LogonUser() method to authenticate against the configured Windows domain. My derived class overrides the AuthenticateToken() method to supply code for the custom credential store, handled by a pseudo-Membership utility shown here:
public class ApplicationUsernameTokenManager :
UsernameTokenManager
{
protected override string AuthenticateToken
( UsernameToken token )
{
return
Membership.Users.GetUserPassword(token.Username);
}
}
The WSE run time expects AuthenticateToken() to return the password for the user, in order to compare it to the password supplied in the SOAP request. If it's a match, authentication is successful. What it doesn't do is set the security principal for the request context, so this is something I coded into the sample as well.
To configure this custom UsernameTokenManager type, head to the WSE configuration tool's Security tab. By adding a valid UsernameTokenManager type to the Security Token Managers section (see Figure 2), a <securityTokenManager> element is added to the <Microsoft.web.services> section of the web.config, as shown here:
<security>
<securityTokenManager type=
"TellerServices.ApplicationUsernameTokenManager,
TellerServices" xmlns:wsse=
"http://schemas.xmlsoap.org/ws/2003/06/secext"
qname="wsse:UsernameToken" />
</security>
The Security Token Manager dialog box shown in Figure 2 provides formatting hints for the Type, Namespace, and QName parts for <securityTokenManager> configuration. Supplying Type information is fairly straightforward, but you should know that Namespace and QName describe the XML element that contains the token. In my example, and in most cases, you will use the WS-Security specification settings that expect username tokens to be passed within the <wsse:Security> header, as shown here:
<wsse:Security soap:mustUnderstand="1"> <wsse:UsernameToken
xmlns:wsu
="http://schemas.xmlsoap.org/ws/2003/06/utility"
wsu:Id="SecurityToken-1577266d-e7a9-4591-a4ad-
a28864dd287c">
<wsse:Username>mlbteller</wsse:Username>
<wsse:Password Type="wsse:PasswordDigest">
RZOCTvC0652aFHD7vW1uY/xfNE8=</wsse:Password>
<wsse:Nonce>
9FIPxdApFcsD+GRrNdx8HQ==</wsse:Nonce>
<wsu:Created>2004-02-23T00:21:09Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
The configured UsernameTokenManager type is automatically instantiated by the WSE run time if a username token is supplied with the request. A SecurityFault exception is issued to the calling client in the form of a SOAP fault if the WSE run time cannot successfully authenticate the user.
To require that the username and password be supplied for specific endpoints, you can write code or define a policy that automatically verifies its presence. To configure a policy that requires username and password credentials for service endpoints, use the policy configuration interface provided with the tool (Figure 1).
Enforcing User Credentials with Policy and Hardly Any Code
The configuration tool simplifies setting up policies for authentication and encryption. The Add/Edit Policy dialog box (Figure 1) provides a drop-down list of supported token types, including UsernameToken. You can configure a specific endpoint, or specify "default" as the Service Location, indicating that this will be the default policy for all service endpoints.
I'll explore more of this dialog box later. For now, let's see if my security requirements are being enforced. After closing the configuration tool, the web.config is updated to reflect that there is now a receiving policy cache, as shown here:
<microsoft.web.services>
<policy>
<send />
<receive>
<cache name="policyCache.xml" />
</receive>
</policy>
</microsoft.web.services>
The policy cache file for receiving endpoints is created at the root directory of the application. The schema for WSE policy configuration requires an outer <policyDocument> element, within which are policy mappings inside a <mappings> element, and a collection of individual WS-Policy settings within a <policies> element (shown in Listing 1). One or more <wsp:Policy> elements can appear within the <policies> section, and each <wsp:Policy> element is an instance of a valid WS-Policy definition.
For example, following the WS-Policy specification, a <wssp:Integrity> element contains the requirements for signing parts of the message, in this case using token type wsse:UsernameToken:
<wst:TokenType
xmlns:wsse="http://
schemas.xmlsoap.org/ws/2003/06/secext"
xmlns:wst="http:
//schemas.xmlsoap.org/ws/2002/12/secext">
wsse:UsernameToken
</wst:TokenType>
By default, the policy is set up to use the token to sign the message body, indicated by the <wssp:MessageParts> element's wsp:Body() function:
<wssp:MessageParts
Dialect= "http://
schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body()</wssp:MessageParts>
With this policy file, the WSE 2.0 run time invokes policy filters in the message pipeline to verify that the message body is signed with a username and password token matching the WS-Security specification. If you invoke the Web service without providing a token, a SoapHeaderException is raised in the form of a SOAP fault to the client.
Passing User Credentials
To properly pass the user token, the client must create an instance of the UsernameToken type, populate it with the correct username and password, and somehow indicate to the client-side run time that it must be serialized to the outgoing message. You can manually add the token to the proxy's RequestSoapContext or configure a client-side policy to enforce behavior through the pipeline. The latter requires that you add the token to a global cache where the WSE run time looks to collect all tokens and signatures for serializing messages to fit policy.
The same policy configuration interface shown in Figure 1 is used to define a policy cache for sending messages from the client. An application configuration file (app.config) is created by the tool to configure send and receive policy files for the client. Policy configuration files are also similar to Listing 1; client policy and service policy need only be complimentary, not identical.
After setting up a send policy for the client, specifying the requirement for outgoing messages to support UsernameToken signatures, the WSE run time looks for an appropriate token in the global cache during serialization. This global cache is accessible through the SecurityTokenCache type in the Microsoft.Web.Services.Security.Tokens namespace. Its purpose is to store tokens for client applications that apply policies to outgoing messages.
Using the global cache is an alternative to adding tokens directly to the Web service proxy class, which I'll use later when I need to deal with a more complicated security scenario. The following code creates a UsernameToken credential and adds it to the global cache:
UsernameToken userToken = new UsernameToken(
this.txtUsername.Text,
this.txtPassword.Text,
PasswordOption.SendHashed);
SecurityTokenCache.
GlobalCache.Add(userToken);
UsernameToken is an object representation of the wsse:UsernameToken type. A username token per the WS-Security specification may provide a plain-text password, a hashed password, or no password at all. The PasswordOption enumeration I used requests that the password be sent hashed, but values exist for the other two options as well, though not recommended for obvious reasons. Passing a username token without a password implies that the user has previously been authenticated within the trust domain of the service. This only works if all applications are part of the same domain, or if a single sign-on solution that handles username tokens is introduced. Sending a password in clear text, of course, has implications if the message is not encrypted.
At this point, with two lines of code on the client, a single authentication method on the server, and some skillful pointing and clicking to configure policy and security, you have user authentication in place.
Extensibility for Hashed Passwords
Allowing the run time to validate the database-provided password against the information passed in the message has a flaw in its logic. Most secure databases actually store one-way hash versions of passwords that cannot be decoded for comparison. That means that either the client must pass a hash of their passwords using the same hashing algorithm, or you need a way to override the way WSE compares the password with the database hash.
It's neither reasonable to require clients to hash passwords with a consistent algorithm, nor is it appropriate, as that publicly exposes the hashed value. So we must rely on the future extensibility of WSE to help us out in this department. Today it is not possible to override how the run time validates passwords.
Controlling Message Privacy: Who Can Read the Message?
If message parts are encrypted with a public key, only the holder of the (well guarded) private key can decrypt and process those parts. Once again using policy configuration, you can quickly add functionality to your Web services, leveraging the WSE run time to handle decryption.
To configure TellerServices to require encryption, check the Require Encryption box (as shown in Figure 1), and select X509SecurityToken from the Token Type list. This adds a <wssp:Confidentiality> element to the policy WS-Policy configuration for the application shown in Listing 1. The security token used for encryption, in this case, is wsse:X509v3. The run time uses this policy setting to verify that the message body is encrypted using this type of token. Subsequently, the run time looks to the configured certificate store for a private key to match the public key token indicated by the message. Assuming the key is found, decryption is handled by the pipeline prior to deserializing the message. The security configuration interface (Figure 2) has an X.509 Certificate Settings section that specifies the certificate store, and further rules for certificate processing.
Once again, a SoapHeaderException is raised if the message does not satisfy security policy, so you need to turn to the client to configure its policy and properly encrypt the message.
Clients use a public key to encrypt messages, and the destination Web service must have access to the matching private key to successfully decrypt. The same configuration tool is used on the client side to specify an encryption policy for outgoing messages, which means that the run time on the client side also looks for a valid encryption token in the global cache. During message serialization, this token is retrieved from the configured certificate store on the client computer to encrypt the message. The default policy configuration sets <wssp:MessageParts> to wsp:Body(), indicating that the message body is the target of the encryption policy. This setting should match whatever the policy is for decryption on the service side.
The following code creates an encryption token and adds it to the global cache:
X509SecurityToken encToken =
CertificateStoreUtil.
GetCertificateToken(
ConfigurationSettings.AppSettings
["serverpublickeyid"]);
SecurityTokenCache.GlobalCache.Add( encToken);
CertificateStoreUtil.GetCertificateToken() encapsulates code to retrieve the public key from the certificate store on the local computer, based on a configured application setting. This assumes that the client has already retrieved the public key for the Web service, and placed it in the certificate store of the client computer.
Encryption policy can be configured for inbound and outbound messages using the same policy configuration tool. In this case, a reverse set of keys are used. The service signs the response with the trusted source' public key and the client application uses its private key to decrypt the response. In situations where message data is highly sensitive, although this adds bloat to the message payload, it also guarantees that no intermediaries or non-trusted parties can read encrypted message parts.
Verifying Message Integrity: Who Really Sent the Message?
Even if a valid set of user credentials are supplied to invoke the WS, and the message is encrypted so that only the ultimate receiver can inspect messages, there still leaves the lack of certainty over the source of the message. Any client with the right public key can encrypt a message, and it is possible that a username and password combination could be hacked. If a limited set of trading partners or applications are considered to be valid Web service clients, it is possible to require each partner to use their private key to sign messages.
When a message is signed, it not only can serve as proof of the message source, but also proof of message integrity. Signing a message part implies that an encrypted version of the message part is sent along with the message so that the receiver can verify that the part was not tampered with. If a message part is signed with a private key, the receiver can decrypt the signed message part using the public key, to verify that the decrypted part matches the message sent.
Requiring Private Key Signatures
The policy configuration tool supports many signing options, including asymmetric X.509 digital signatures. This type of signature is the one sure way to guarantee the sender of the message, without having to maintain a shared secret between sender and receiver (as in a symmetric authentication pattern like passing username and password tokens). So long as the service has access to the public key for the sender, the run time can automatically verify that the required message parts are correctly signed by a trusted source.
On the client side, a similar policy is configured for sending encrypted messages, but in this case, the private key for the client application is retrieved and stored in the global cache:
X509SecurityToken sigToken =
Certificates.CertificateStoreUtil.
GetCertificateToken(
ConfigurationSettings.AppSettings
["clientprivatekeyid"]);
SecurityTokenCache.GlobalCache.Add(sigToken);
By default, the policy setting applies signatures to the body of the message only. The WS-Policy specification supports signing specific headers in addition to the body, but with WSE 2.0, this must be configured manually by editing the policy XML.
Signing Multiple Message Parts
You can manually edit the policy file for a Web service or client to specify that multiple message parts are signed by the run time during message serialization, and are properly evaluated during deserialization.
There are two dialects for specifying message parts within the <wssp:MessageParts> element: XPath 1.0 (indicated by Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part") and pre-defined functions supplied in the WS-PolicyAssertions specification (indicated by Dialect="http://www.w3.org/TR/1999/REC-xpath-19991116"). The latter of the two is preferred where possible.
Following the preferred way, it is possible to sign the body, or any header of the SOAP message using these functions: wsp:Body(), wsp:Header(). Headers are described by their Qname; for example wsp:Header(wsa:To) refers to the WS-Addressing <wsa:To> element. In a space-delimited list, to sign a list of WS-Addressing headers along with the body element, the <wssp:MessageParts> element can be customized as follows,:
<wssp:MessageParts Dialect=
"http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body() wsp:Header(wsa:To)
wsp:Header(wsa:Action) wsp:Header(wsa:MessageID)
wsp:Header(wsa:From)
</wssp:MessageParts>
This change also requires that the WS-Addressing namespace be declared, which can be done in the <wsp:Policy> element as follows:
<wsp:Policy wsu:Id=
"policy-60a43419-c3d2-40c0-994c-fd9cae47ef46"
xmlns:wsp=
"http://schemas.xmlsoap.org/ws/2002/12/policy"
xmlns:wsa=
"http://schemas.xmlsoap.org/ws/2003/03/addressing"
>
Unfortunately, in the current state of the pre-release of WSE 2.0 the tool can no longer load the policy file once this change has been made. I suspect this will be fixed before its final release, but regardless the policy is valid and can be properly parsed by the WSE run time.
It is also recommended that you use a message predicate assertion to enforce that headers requiring a signature are present in the message. Message predicate support must be manually added to the policy file at this time, which also means that it breaks the tool's ability to view policy. The following snippet shows an assertion that requires the presence of a SOAP body, and WS-Addressing headers To, Action, MessageID and From:
<wsp:MessagePredicate wsp:Usage="wsp:Required"
Dialect=
"http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body() wsp:Header(wsa:To)
wsp:Header(wsa:Action) wsp:Header(wsa:MessageID)
wsp:Header(wsa:From)
</wsp:MessagePredicate>
Requiring Multiple Signatures
I've shown you how to use a private key to sign messages to prove that the source of the message is a valid trading partner. In addition, you looked at providing username tokens to identify the application user. But, how do you do both? Using the pre-release WSE 2.0 policy configuration tool, this is not possible. So I took a crack at modifying the policy file by hand, to provide multiple policies for the same endpoint, or to provide multiple token signatures. My attempt failed the XML schema validation for the policy file, which means I also had no luck getting the run time to respect it.
Although in theory this effort should be possible according to the WS-Policy specification, there are still some growing pains you'll experience when you try to add complexity to the way you apply these standards through early adoption tools like WSE 2.0. Because I was unable to use policy to generate the correct XML directly from the client side, I removed the policy from the client altogether and moved to a hand-built implementation that passed a username token and digital signature along with full body encryption.
On the client side, the WSE Web service proxy provides a RequestSoapContext and ResponseSoapContext that make it possible to set and retrieve values applicable to each. To send a message that includes a UsernameToken without supplying a policy after creating the token object, it is added to the RequestSoapContext.Security.Tokens collection. In addition, because WSE automatically includes this token in the <wsee:Security> element, it is required that you indicate the intention to sign the message with the token by creating a Signature object from the message and adding it to the RequestSoapContext.Security.Elements collection:
UsernameToken userToken = new UsernameToken(
this.txtUsername.Text,
this.txtPassword.Text,PasswordOption.SendHashed);
svc.RequestSoapContext.Security.Tokens.Add(
userToken);
svc.RequestSoapContext.Security.Elements.Add(
new Signature( userToken) );
The WSE run time takes information supplied to the proxy at run time, and serializes it according to the appropriate XML specification. By default, signatures are applied to the following WS-Addressing headers: wsa:Action, wsa:From, wsa:To, wsa:MessageID, wsu:Timestamp, wsu:Created, wsu:Expires, wsee:UsernameToken, and soap:Body. In fact, although policy configuration automates many things, by default only the SOAP body is signed unless you edit the policy file manually.
The following code specifies the digital signature token to be applied as a signature as well:
X509SecurityToken sigToken =
Certificates.CertificateStoreUtil.
GetCertificateToken(ConfigurationSettings.
AppSettings["clientprivatekeyid"]);
svc.RequestSoapContext.Security.Tokens.Add(
sigToken);
svc.RequestSoapContext.Security.Elements.Add(
new Signature(sigToken) );
And finally, to supply encryption to outgoing messages:
X509SecurityToken encToken =
Certificates.CertificateStoreUtil.
GetCertificateToken(ConfigurationSettings.
AppSettings["serverpublickeyid"]);
EncryptedData encData = new
svc.RequestSoapContext.Security.Tokens.Add(
encToken);
EncryptedData(encToken) ;
svc.RequestSoapContext.Security.Elements.Add(
encData);
Note that security tokens are added to the security tokens collection for the request context prior to using the token for encryption or message signing as discussed earlier. For encryption, an EncryptedData element is added to the RequestSoapContext.Security.Elements collection.
To enforce policy on the Web services side, for all three security elements, the best I could do with the WSE 2.0 tool is supply a policy for X.509 signatures and encryption, manually edit the policy file to specify body and header parts to sign, and supply a username token from the calling client without the use of policy enforcement. Because the UsernameTokenManager is invoked even if the service policy does not require it, I can rely on the username token being validated regardless. Unfortunately, since the policy file does not verify the presence of the username token in this scenario, I must write code to verify whether a security principal has been set before any of my Web service methods execute. You'll see this in the sample code for this article.
Preventing Message Replay: Has This Message Been Sent Before?
After ensuring that the caller is trusted, authenticating the invoking user, and signing and encrypting sensitive parts of a message exchange, there is still the issue of message replay. You can prevent this problem through several approaches. Servers can check for a repeating nonce (sent with symmetric authentication) in relation to the WS-Timestamp value, to see if the message has been replayed. To ensure no tampering with the applicable message parts, a signature is applied to the WS-Timestamp header and applicable tokens.
In a very large-scale solution, sophisticated XML firewalls manage the inspection of messages to detect replay; IP-level Denial of Service (DoS) checking is also performed at the network perimeter, using WSE 2.0 extensibility features that you can provide to custom policy assertions and build your own replay cache. Such an example is provided with the WSE 2.0 pre-release samples, so I integrated this code with my final solution to supply you with a complete scenario that also addresses this final layer of security.
Summary
The WSE 2.0 release takes a great crack at demystifying the emerging WS* standards, and provides extensibility for developers to create their own solutions, if necessary, to remain interoperable and keep up with rapid change. In this article, I explored how to leverage WSE 2.0 support for WS-Policy to implement various layers of OASIS WS-Security features, how to manipulate policy files to add support not provided through the WSE tool interface, and how to overcome some of the limitations that exist with the tool today.