Managing the lifecycle of security tokens (Geneva, STS, WCF...)

 
One of the things I didn't like of the WSFederationHttpBinding is that it encapsulates lots of things. In particular, the call against the STS to obtain a SAML token. I wanted to have control over that process.  The good news is that the Geneva Framework allow us to do all that in a very simple fashion (after using some Lutz :)).
So, forget about custom WCF IssuedSecurityTokenProviders, and welcome WSTrustClient and FederatedClientCredentials!
 

Calling your STS to obtain a token

 
The first thing you might want to do is call your STS to obtain a token. This is the simplest way to do it:
 
private SecurityToken GetIdentityProviderToken()
{
    var client = new WSTrustClient(
                                    this.identityProviderActiveStsBinding,
                                    this.identityProviderActiveStsUrl,
                                    System.ServiceModel.Security.TrustVersion.WSTrustFeb2005,
                                    new System.ServiceModel.Description.ClientCredentials());

    client.ClientCredentials.ServiceCertificate.DefaultCertificate = this.identityProviderStsCertificate;
    var request = new RequestSecurityToken(RequestTypeConstants.Issue);
    request.AppliesTo = this.identityProviderAppliesToUrl;

    var identityToken = client.Issue(request);
    client.Close();
    return identityToken;
}

 

Overriding the ClientBase to inject the security token with Geneva

Once you have the token you want to inject that token into your client proxy. The code below shows a nice and clean way to inject the SAML token into the WCF channel. Notice the ctor takes a dependency on a custom interface ISecurityTokenProvider. This is a very simple interface I created with a single method GetSecurityToken. On channel construction we are going to pass the proxy through Geneva CreateChannelWithIssuedToken method. This will change the ClientCredentials of the channel to use the FederatedClientCredentials that will shortcircuit the IssuedSecurityTokenProvider of the binding by returning the "external" security token.

public class FederatedCatalogServiceClient : ClientBase<ICatalogService>, ICatalogService
{
    private ISecurityTokenProvider tokenProvider;

    public FederatedCatalogServiceClient(ISecurityTokenProvider tokenProvider, ...)
    {
        this.tokenProvider = tokenProvider;
    }

    // operations ...

    protected override ICatalogService CreateChannel()
    {
        ICatalogService channel;
        lock (this.ChannelFactory)
        {
            FederatedClientCredentials.ConfigureChannelFactory(this.ChannelFactory);
            channel = ChannelFactoryOperations.CreateChannelWithIssuedToken(
                this.ChannelFactory,
                this.tokenProvider.GetSecurityToken());
        }

        return channel;
    }
}

 

Custom Binding to call a service that requires a token

Finally, since we don't want to use WSFederationBinding anymore because it will grab the token for us, the code below will create a binding "similar" to the federation binding but won't never call our STS. We still have to provide some configuration settings like the urls so the binding can be constructed, but they won't be used on runtime.

public Binding CreateBinding()
{
    var finalBinding = new CustomBinding();

    var bindingProtection = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.Thumbprint);
    bindingProtection.ReferenceStyle = SecurityTokenReferenceStyle.Internal;
    bindingProtection.InclusionMode = SecurityTokenInclusionMode.Never;
    bindingProtection.X509ReferenceStyle = X509KeyIdentifierClauseType.Thumbprint;

    var security = new SymmetricSecurityBindingElement(bindingProtection);

    security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
    security.RequireSignatureConfirmation = true;

    var issuerBinding = new CustomBinding();

    issuerBinding.Elements.Add(new TransactionFlowBindingElement());

    var protection = new SecureConversationSecurityTokenParameters();
    var issuerSecurity = new SymmetricSecurityBindingElement(protection);
    issuerSecurity.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;

    issuerBinding.Elements.Add(issuerSecurity);

    security.EndpointSupportingTokenParameters.Endorsing.Add(
        new IssuedSecurityTokenParameters(
            "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1",
            new EndpointAddress("http://notused"),
            issuerBinding)
        {
            IssuerMetadataAddress = new EndpointAddress("http://notused/mex")
        });

    security.ProtectionTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.Thumbprint, SecurityTokenInclusionMode.Never);

    var protectionSecurityProtectionParameters = new SspiSecurityTokenParameters();
    protectionSecurityProtectionParameters.RequireCancellation = true;
    security.ProtectionTokenParameters.InclusionMode = SecurityTokenInclusionMode.Never;

    var protectionSecurity = new SymmetricSecurityBindingElement(protectionSecurityProtectionParameters);
    protectionSecurity.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;

    protection.BootstrapSecurityBindingElement = protectionSecurity;

    finalBinding.Elements.Add(security);

    issuerBinding.Elements.Add(new TextMessageEncodingBindingElement());
    issuerBinding.Elements.Add(new HttpTransportBindingElement());

    finalBinding.Elements.Add(new TextMessageEncodingBindingElement());
    finalBinding.Elements.Add(new HttpTransportBindingElement());

    return finalBinding;
}

Happy Federation! :)

blog comments powered by Disqus