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

    Published by Matias Woloski on December 14th, 2008 3:16 am under Geneva, Identity Management, Security Token Service, WCF, Zermatt

    5 Comments

     
    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! :)

  • 5 Comments:

    1. Scott McFadden said on January 15, 2009:

      How would you implment that custom binding in config file format?

      thanks

    2. Martin said on February 10, 2009:

      Can you please provide an example of how to use this via a web.config? Would one be able to utilize all other federated settings via the config and not programmatically

    3. Martin said on February 25, 2009:

      Could you help me out? It’s in regard to your article. I posted a question here @ http://social.msdn.microsoft.com/Forums/en-US/Geneva/thread/aeb8ecba-2773-436f-a1c1-3b96544ff3b3

    4. Steve said on February 26, 2009:

      Could I take the SecurityToken returned from GetIdentityProviderToken and somehow create a ClaimsPrincipal and ClaimsIdentity to apply to Thread.CurrentPrincipal? This would allow me to use my STS for authentication and authorization in a Windows Forms app. Or is there a more appropriate way to achieve this?

    5. Matias Woloski’s Blog » Blog Archive » Getting a token from ADFS (ex Geneva Server) using WCF said on July 17, 2009:

      [...] You can use this together with the CreateChannelWithIssuedToken extension method (as shown in a previous post). [...]

    Leave a comment

    Your email address will not be published.

Tags