Monthly Archives: July 2014

Adding envelope headers to SOAP calls using WSE 3

This is a little old school as greenfield projects are more likely to use WCF or some other mechanism – but this blog is all about reminding myself how things work. In this case I’m going to look back at how WSE3 can be used to add data to the header of a SOAP envelope for web service calls.

WSE or Web Service Enhancements (from Microsoft) allows us to intercept SOAP messages and in the case of this post, add information to the SOAP message which can then be read by a service.

A likely scenario and one I will describe here is that we might wish to add a security token to the SOAP message which can be used by the service to ensure the user is authenticated.

Let’s get started

If you’ve not already got WSE3 on your machine then let’s get it, by either downloading WSE3 and then referencing the following assembly

Microsoft.Web.Services3

or use NuGet to add the WSE package by using

Install-Package Microsoft.Web.Services3

Configuration

Next we need to create some config. So add/edit your App.config to include the following

<configSections>
   <section name="microsoft.web.services3" 
        type="Microsoft.Web.Services3.Configuration.WebServicesConfiguration, 
        Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
        PublicKeyToken=31bf3856ad364e35" />
</configSections>

<microsoft.web.services3>
   <policy fileName="wse3ClientPolicyCache.config" />
      <diagnostics>
         <trace enabled="false" 
                input="InputTrace.webinfo" 
                output="OutputTrace.webinfo" />
         <detailedErrors enabled="true" />
   </diagnostics>
</microsoft.web.services3>

Notice we’ve stated the policy filename is wse3ClientPolicyCache.config (obviously this can be named whatever you want). This file will contain the config which denotes what extensions are to be executed during a web service call.

So let’s look at my wse3ClientPolicyCache.config file

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
  <extensions>
    <extension name="ssoTokenAssertion" 
               type="SsoService.SsoTokenAssertion, SSOService"/>
  </extensions>
  <policy name="SsoPolicy">
    <ssoTokenAssertion />
    <requireActionHeader />
  </policy>
</policies>

Here we’ve added an extension named ssoTokenAssertion which is associated with a type in the standard form (type, assembly). We then create a policy which shows we’re expecting the policy to use to extension ssoTokenAssertion that we’ve just added. The requireActionHeader is documented here.

Changes to the web service code

So we’ve now created all the config to allow WSE to work but we now need to write some code.

We need to do a couple of things to allow our web service code (whether generated via wsdl.exe or hand coded) to work with WSE. Firstly need to derive our service from Microsoft.Web.Services3.WebServicesClientProtocol and secondly we need to adorn the web service with the Microsoft.Web.Services3.Policy attribute.

In our configuration we created a policy named SsoPolicy, this should be the string passed to the Policy attribute. Here’s an example web service

[Microsoft.Web.Services3.Policy("SsoPolicy")]
public partial class UserService : WebServicesClientProtocol
{
   // implementation
}

In the above I’ve removed the wsdl generated code for simplicity.

Implementing our “SsoService.SsoTokenAssertion, SsoService” type

So we’ve got the configuration which states that we’re supplying a type SsoTokenAssertion which will add data to the SOAP envelope header. Firstly we create a SecurityPolicyAssertion derived class.

public class SsoTokenAssertion : SecurityPolicyAssertion
{
   private const string SSO_TOKEN_ASSERTION = "ssoTokenAssertion";

   public override SoapFilter CreateClientInputFilter(FilterCreationContext context)
   {
      return null;
   }

   public override SoapFilter CreateClientOutputFilter(FilterCreationContext context)
   {
      return new SsoClientSendFilter(this);
   }

   public override SoapFilter CreateServiceInputFilter(FilterCreationContext context)
   {
      return null;
   }

   public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context)
   {
      return null;
   }

   public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions)
   {
      bool isEmpty = reader.IsEmptyElement;
      reader.ReadStartElement(SSO_TOKEN_ASSERTION);
      if (!isEmpty)
      {
         reader.ReadEndElement();
      }
   }

   public override IEnumerable<KeyValuePair<string, Type>> GetExtensions()
   {
      return new[] { new KeyValuePair<string, Type>(SSO_TOKEN_ASSERTION, GetType()) };
   }
}

In the above code, we’re only really doing a couple of things. The first is creating an output filter, this will add our SSO token to the SOAP envelope header on it’s way to the server, the ReadXml really just throws away any token that might be sent to our code – in this case we don’t care about a token in the header but we might for some other application.

public class SsoClientSendFilter : SendSecurityFilter
{
   protected const string SSO_HEADER_ELEMENT = "ssoToken";

   public SsoClientSendFilter(SecurityPolicyAssertion parentAssertion) :
      base(parentAssertion.ServiceActor, true)
   {
   }

   public override void SecureMessage(SoapEnvelope envelope, Security security)
   {
      string ssoTokenString = SsoManager.TokenString;

      if (String.IsNullOrEmpty(ssoTokenString))
      {
          throw new ApplicationException(
 	   "Could not generate an SSO token. Please ensure your user name exists within SSO and the password matches the one expected by SSO.");
      }

      XmlElement ssoTokenElement = envelope.CreateElement(SSO_HEADER_ELEMENT);
      ssoTokenElement.InnerText = ssoTokenString;
      envelope.Header.AppendChild(ssoTokenElement);
   }
}

The above code is probably fairly self-explanatory. The SsoManager class is a singleton which simply authenticates a user and creates a token string which we will attach to the envelope header, which we do by creating an XmlElement and adding it to the header.

Further Reading

Just came across Programming with WSE which has some interesting posts.