In a Federation Scenario a client might want to access the services by using a SAML token that was issued to it by a STS. The service in turn might have to call other services (like a intermediary) to fulfill the request. When calling the backend service the service might want to use the SAML token that was presented to it by the client. This is a very common enterprise scenario. WCF currently does not enable this scenario. You can get around this by writing some custom code on the service side. Basically you need to write a custom SAML assertion that will remember the stream and will write it out when it has to. This also involves registering your own serializer and so on. Below is some code samples,

Write a Custom SAML Assertion

 

public class CustomSamlAssertion : SamlAssertion

{

    MemoryStream ms;

    public override void ReadXml(XmlDictionaryReader reader, SamlSerializer samlSerializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)

    {

        ms = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadOuterXml()));

        ms.Position = 0;

         XmlDictionaryReader dicReader = XmlDictionaryReader.CreateTextReader(ms, XmlDictionaryReaderQuotas.Max);

         base.ReadXml(dicReader, samlSerializer, keyInfoSerializer, outOfBandTokenResolver);

    }

    public override void WriteXml(XmlDictionaryWriter writer, SamlSerializer samlSerializer, SecurityTokenSerializer keyInfoSerializer)

    {

         if (ms != null)

         {

             ms.Position = 0;

             XmlDocument dom = new XmlDocument();

             dom.Load(ms);

             dom.DocumentElement.WriteTo(writer);

             return;

         }

         base.WriteXml(writer, samlSerializer, keyInfoSerializer);

     }

}

 

 

The above assertion just stores the incoming SAML Assertion in a memory stream and writes out the stream when you try to re-send the assertion. Note, if you want to create a new SAML assertion you will have to new up the built in SAML Assertion. The way signature processing is handled on the send side will prevent from writing out the signature if the CustomSamlAssertion is new'ed up to build a new assertion. The next step would be to provide a custom SAML serializer,

Write a Custom SAML Serializer 

 

public class CustomSamlSerializer : SamlSerializer

{

     public override SamlAssertion LoadAssertion(XmlDictionaryReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)

     {

           CustomSamlAssertion assertion = new CustomSamlAssertion();

           assertion.ReadXml(reader, this, keyInfoSerializer, outOfBandTokenResolver);

           return assertion;

       }

}

 

 

Now we need to plug the Custom Serializer with the way Token Serialization is handled in WCF. So we need to write a

Write a Custom Token Serializer

 

public class CustomTokenSerializer : WSSecurityTokenSerializer

{

       protected override void WriteTokenCore(XmlWriter writer, SecurityToken token)

       {

            if (token is SamlSecurityToken)

            {

                 SamlAssertion assertion = ((SamlSecurityToken)token).Assertion;

                 if (assertion is CustomSamlAssertion)

                 {

                      XmlDictionaryWriter dicWriter = XmlDictionaryWriter.CreateDictionaryWriter(writer);

                      ((CustomSamlAssertion)assertion).WriteXml(dicWriter, new SamlSerializer(), WSSecurityTokenSerializer.DefaultInstance);

                       return;

                   }

               }

               base.WriteTokenCore(writer, token);

         }

}

 

 

The above code delegates all token serialization to the base class except for SAML.

Next, we need to provide a TokenManager that gives out our Custom Serializer instead of the default serializer.

Write a Custom Token Manager

 

public class CustomTokenManager : ClientCredentialsSecurityTokenManager

{

          public CustomTokenManager(CustomClientCredentials clientCredentials)

                 : base(clientCredentials)

           {

                     this.tokenProvider = new SamlTokenProvider(token as SamlSecurityToken);

           }

           public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)

           {

                   return new CustomTokenSerializer();

           }

}

 

 

All Credentials related stuff should end up in ClientCredentials or ServiceCredentials in object in WCF. So let's implement a Custom Client Credentials that wraps the Token Manager.

Write Custom Client Credentials

 

public class CustomClientCredentials : ClientCredentials

{

    SecurityToken securityToken;

    public override SecurityTokenManager CreateSecurityTokenManager()

    {

        return new CustomTokenManager(this);

    }

    protected override ClientCredentials CloneCore()

    {

       return this;

    }

}

 

When you are receiving the SAML token (you are the service) all that you need is the custom SAML Serializer. Below is how you would configure this,

 

ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService));

serviceHost.Credentials.IssuedTokenAuthentication.SamlSerializer = new CustomSamlSerializer();

serviceHost.Open();

 

 

Now any SAML token received via this serviceHost will be loaded into the Custom SAML Assertion we have created.

When you want to re-serialize the SAML token, you have to register your Custom Client Credentials with the Channel Factory (Note: you will be acting as a client in this case). Below is how you would configure this,

 

EndpointAddress er = new EndpointAddress(new Uri(backEndServiceUri), EndpointIdentity.CreateDnsIdentity("Server-Cert"));

ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(GetCustomBinding(), er);

CustomClientCredentials clientCredentials = new CustomClientCredentials();

factory.Endpoint.Behaviors.Remove<ClientCredentials>();

factory.Endpoint.Behaviors.Add(clientCredentials);

ICalculator client = factory.CreateChannel();

 

That's it, you can now receive SAML tokens and re-serialize the token to a backend service.

 The attached project has code for this scenario.