views:

128

answers:

1

I have:

  • A passive STS "login application" that is also an identity provider.
  • An active STS WCF service that can accept and handle ActAs tokens
  • A web site relying party
  • A WCF service relying party that is called by the web site.

All of this is put together using Windows Identity Foundation and custom STS code. Active Directory (ADFS) is not involved.

What I have working now is:

  1. User attempts to visit web site RP.
  2. User gets redirected to passive STS.
  3. User logs in, gets a token issued, gets redirected back to the web site RP.
  4. Web site RP makes a service call to the WCF RP and passes an ActAs token so delegation happens.
  5. Active STS sees the ActAs token come in and properly sets up the output identity so the primary identity is the ActAs token and the caller's identity is added to the Actor chain.
  6. WCF RP gets the proper token with everything in place, current thread principal has the right identity and claims as should be.

I want the WCF RP to request additional claims from the active STS.

That is, in the RST that goes to the active STS, I want it to include the list of claims that the service requires so those additional claims can be fetched if they're not already present.

I have figued out how to do this by modifying the binding on the web site RP client but I want the requirements to be specified on the WCF RP service end.

I have a feeling it has something to do with the binding I'm using. I had trouble getting ws2007FederationHttpBinding working with ActAs tokens and all of the examples in the WIF Identity Training Kit used customBinding, so I did that, too, and it finally worked. Here is the config snippet from the WCF RP showing my binding configuration:

<system.serviceModel>
  <bindings>
    <customBinding>
      <binding name="CustomBinding_FederatedService">
        <security
          authenticationMode="IssuedTokenForCertificate"
          messageSecurityVersion="WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10">
          <issuedTokenParameters tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1"&gt;
            <issuer address="http://localhost:38901/ActiveSts.svc/IWSTrust13" />
            <issuerMetadata address="http://localhost:38901/ActiveSts.svc/mex" />
          </issuedTokenParameters>
        </security>
        <textMessageEncoding>
          <readerQuotas maxArrayLength="32767" />
        </textMessageEncoding>
        <httpTransport />
      </binding>
    </customBinding>
  </bindings>
</system.serviceModel>

If I change the config on the calling web site to indicate claimTypeRequirements in the issuedTokenParameters section, the Active STS actually does see the list of required claims in the RST... but that's on the calling web site, which is problematic for me.

How do I make it so the WCF RP can specify additional claims it requires without having to duplicate that configuration on the calling web site?

If it is, indeed, a binding issue, it would help if you can show me the equivalent configuration given what I've got above. I can update the web site and the WCF service with the appropriate changes, but again, I need the service (or a behavior on the service, or configuration on the service) to control the list of claims it needs. The service should not accept requests that are missing required claims.

A: 

It turns out the way you have to do this is to...

  1. Figure out a way to retrieve the claim requirements on the client side. This could be some sort of central configuration service, WS-Policy/Metadata Exchange, or whatever you like.
  2. Manually create the token request for the STS. Rather than use the Microsoft.IdentityModel CreateChannelActingAs(token) extension method, manually request an ActAs token (or a new token) using WSTrustChannelFactory.
  3. Add the manually requested token to the outgoing channel parameters.

Note that this doesn't exactly remove the need for your client to know about the list of claims requirements, but it does give you the ability to centralize that configuration somehow or even use the service itself to provide that list of claims requirements. There is, unfortunately, nothing in the Microsoft.IdentityModel stack that does all this for you. The client absolutely needs to know the list of claims requirements because the request for security token is issued as part of the client communication, not issued by the service as service operation requests come in.

Anyway, you can see some decent explanations of WSTrustChannelFactory and WSTrustChannel on the MSDN web site. My solution is based on that.

Boiled down without all the error handling, etc., the code basically looks like this:

// You need the channel factory so you can get info about the endpoint.
var factory = new ChannelFactory<IService>();

// Get the issuedTokenParameters information from the binding.
// You see this in the XML config but it's painful to access.
var tokenParameters = factory.Endpoint.Binding
    .CreateBindingElements()
    .OfType<SecurityBindingElement>().First()
    .EndpointSupportingTokenParameters
    .Endorsing.OfType<IssuedSecurityTokenParameters>().First();

// Prepare the RST.
var trustChannelFactory = new WSTrustChannelFactory(tokenParameters.IssuerBinding, tokenParameters.IssuerAddress);
var trustChannel = (WSTrustChannel)trustChannelFactory.CreateChannel();
var rst = new RequestSecurityToken(RequestTypes.Issue);
rst.AppliesTo = factory.Endpoint.Address;

// If you're doing delegation, set the ActAs value.
var principal = Thread.CurrentPrincipal as IClaimsPrincipal;
var bootstrapToken = principal.Identities[0].BootstrapToken;
rst.ActAs = new SecurityTokenElement(bootstrapToken);

// Here's where you can look up claims requirements dynamically.
rst.Claims.Add(new RequestClaim("http://dynamically-added-claim"));

// Get the token and attach it to the channel before making a request.
RequestSecurityTokenResponse rstr = null;
var issuedToken = trustChannel.Issue(rst, out rstr);
var fccParameters = new FederatedClientCredentialsParameters();
fccParameters.IssuedSecurityToken = issuedToken;
var channel = factory.CreateChannel();
((IChannel)channel).GetProperty<ChannelParameterCollection>().Add(fccParameters);

// NOW you can make the request.
channel.DoWork();

This also allows you to cache that issued token if you so desire to optimize some of the communication flowing around the system.

Granted, if you're not trying to dynamically insert claim requirements or if you're otherwise happy using the XML config and duplicating it on the server and the client, this isn't necessary. The CreateChannelActingAs(token) extension method and the whole Microsoft.IdentityModel stack takes care of this for you.

Travis Illig