Before I dive into what went wrong, it might be helpful to explain what OBO is typically used for and why it's important. An STS (especially an IP-STS) is a core service that is as critical to your organization's ability to conduct business as DNS, DHCP, and your enterprise directory. Just like any of these, if your STS is compromised, your business will stop. For this reason, it is critically important to ensure that it is protected. One way to do this is to hide it behind a firewall and proxy it out to the Internet using an intermediary. The resulting network topology looks something like this:
In this type of deployment, the proxy STS lives in the DMZ and external clients hit this not the actual STS; It (and its private signing key) are safely tucked away in your corporate haven.
When a request for a security token arrives to the proxy and it is forwarded to the STS, what's sent is an OBO message. In other words, the proxy is requesting a security token on behalf of the subject. This allows the STS to decide if it should issue a token for the subject by way of the intermediary. This manifests itself as a SOAP message that has a security token for the proxy in the header and another that is embedded in the OBO element of the RST (which is in the body). For example, an OBO request would look something like this:
<!-- These are the credentials of the proxy -->
<!-- These are the credentials of the subject -->
With that groundwork in place, let's look at why PingFederate will not work w/ WIF.
Starting where I left off yesterday, I added a new method to my driver that would get an OBO token from PingFederate:
private static RequestSecurityTokenResponse RequsetOnBehalfOfToken()
var factory = GetChannelFactory();
var rst = new RequestSecurityToken(WSTrustFeb2005Constants
AppliesTo = new EndpointAddress(appliesTo),
OnBehalfOf = new SecurityTokenElement(new UserNameSecurityToken(
var channel = factory.CreateChannel();
channel.Issue(rst, out rstr);
Note how the RST includes an OBO element that has a security token identifying the subject. That is different from the credentials of the proxy/requester that are set when the channel factory is created:
private static WSTrustChannelFactory GetChannelFactory()
// See yesterday's post for more complete code sample.
var factory = new WSTrustChannelFactory(binding, endpoint);
factory.Credentials.UserName.UserName = proxyUserName;
factory.Credentials.UserName.Password = proxyPassword;
When I ran this code, PingFederate returned a fault and printed this in its logs:
2010-03-17 12:05:21,481 tid:066b8dea8 DEBUG [org.sourceid.saml20.bindings.LoggingInterceptor] Handle Exception (http://schemas.xmlsoap.org/soap/envelope/).
org.sourceid.wstrust.handlers.WSTrustException: STS requests with more than one token are unsupported.
As the message says, PingFederate allows only one token in an RST. If an OBO element is included in the RST, it requires one of two things to be true:
- There must be no additional security tokens in the SOAP header, and the security token of the subject must be embedded directly in the OBO element of the RST; or
- The OBO element must contain a SecurityTokenReference element that points to the security token of the subject in the SOAP header.
Option one is not possible because WIF will throw an exception if a client credential is not supplied when calling Issue on a WSTrustChannelFactory object. WIF does not support the SecurityTokenReference element, so option two is out as well. This means that to use WIF to implement the requester, we have to skip OBO entirely, put the subject's identity in the SOAP header, and authenticate the requester to the STS using transport-level security (e.g., using mutually authenticated SSL).