Re-Serialize SAML token


 


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.


 

ReSerializeSaml.zip

Comments (19)

  1. Bob Owen says:

    Hi Govind,

    Once we fixed the serialisation problem it all worked.

    However I implemented this slightly differently to your example.

    Instead of creating a new Token Serialiser, I did the following:

    In the Custom Saml Serialiser I overrode WriteToken like this:

           public override void WriteToken(SamlSecurityToken token, XmlWriter writer, SecurityTokenSerializer keyInfoSerializer) {

               XmlDictionaryWriter dictionaryWriter = XmlDictionaryWriter.CreateDictionaryWriter(writer);

               token.Assertion.WriteXml(dictionaryWriter, this, keyInfoSerializer);

           }

    and in the Customer Token Manager I overrode CreateSecurityTokenSerializer like this:

          public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version) {

               if (version.GetSecuritySpecifications().Contains(WsseSecExt11Namespace)) {

                   return

                       new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11, version.GetSecuritySpecifications().Contains(BspNamespace),

                                                     new CustomSamlSerializer());

               }

               if (version.GetSecuritySpecifications().Contains(WsseSecExt10Namespace)) {

                   return

                       new WSSecurityTokenSerializer(SecurityVersion.WSSecurity10, version.GetSecuritySpecifications().Contains(BspNamespace),

                                                     new CustomSamlSerializer());

               }

               throw new NotSupportedException(Resources.SecurityTokenVersionNotSupported);      

          }

    this is basically a lift from the framework code excpet that I pass in our new Saml Serialiser. Also, I can’t do the down cast to MessageSecurityTokenVersion because it is Internal, which is pretty annoying.

    I will have to check that I am using the correct thing for the first two parameters.

    Also in the CustomSamlAssertion I did the following to save spinning up an XmlDocument each time:

           private string _originalSamlText;

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

                                        SecurityTokenResolver outOfBandTokenResolver) {

               reader.MoveToContent();

               _originalSamlText = reader.ReadOuterXml();

               using (MemoryStream samlStream = new MemoryStream(Encoding.UTF8.GetBytes(_originalSamlText))) {

                   XmlDictionaryReader xmlDictionaryReader = XmlDictionaryReader.CreateTextReader(samlStream, XmlDictionaryReaderQuotas.Max);

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

               }

           }

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

               if (_originalSamlText != null) {

                   writer.WriteRaw(_originalSamlText);

                   return;

               }

               base.WriteXml(writer, samlSerializer, keyInfoSerializer);

           }

    Does this solution look OK?

    Cheers,

    Bob

  2. govindr says:

    Hi Bob,

      That looks fine. I guess you have over written LoadAssertion method of the Custom SAML Assertion too, right?

    – Govind

  3. Bob Owen says:

    Yes, our CustomeSamlSerializer has LoadAssertion overriden to hook in our CustomSamlAssertion.

    Everything appears to be working fine.

    By the way we found another issue if you have an empty (SAML) AttributeValue tag in an (SAML) Attribute. The .Net SamlAttibute blows up with a nasty XML read error as it gets lost in the XML.

    It should probably ignore the empty AttributeValue and then throw a more appropraite error if no valid AttributeValues are specified.

    Thanks for your help,

    Bob

  4. govindr says:

    Hi Bob,

     We depend on the XML Serializer to throw most of the bugs in serialization. It is our first line of defense. I will setup a repro and check the exception message. Thanks for your feedback!

    Govind

  5. nhamer says:

    Hi,

    I have been playing around with your code, but there is one thing I can’t work out.

    Where do you store the Saml Assertion until the service is really the make its backend call?

    I had assumed that you could store the Saml Assertion somewhere in the current OperationContext in the body of SamlSerializer.LoadAssertion method. However I have discovered that the operation context has not been setup when the serializer runs.

    Any thoughts?

  6. govindr says:

    Hi,

      If you plug in your custom serializer described in the post then the SAML token will be parsed using the custom serializer and will be part of the OperationContext. Based on how the SAML token was used in the incoming message you can find it at OperationContet.Current.RequetContext.RequestMessage.Properties.Security.ProtectionToken or IncomingSupportingTokens collection.

    Thanks,

    Govind

  7. Ray says:

    I am attempting to implement this but in the CustomTokenManager how do I implement the following line:

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

    I do not have a tokenProvider property.

    Thanks,

    Ray

  8. govindr says:

    Hi Ray,

      I have shown below how the SamlTokenProvider code can be implemented. Let me know if you have any more questions.

    public class SamlTokenProvider : SecurityTokenProvider

       {

           SamlSecurityToken token;

           public SamlTokenProvider(SamlSecurityToken token)

           {

               this.token = token;

           }

           protected override SecurityToken GetTokenCore(TimeSpan timeout)

           {

               return token;

           }

       }

    – Govind

  9. govindr says:

    Hi Ray,

      I have shown below how the SamlTokenProvider code can be implemented. Let me know if you have any more questions.

    public class SamlTokenProvider : SecurityTokenProvider

       {

           SamlSecurityToken token;

           public SamlTokenProvider(SamlSecurityToken token)

           {

               this.token = token;

           }

           protected override SecurityToken GetTokenCore(TimeSpan timeout)

           {

               return token;

           }

       }

    – Govind

  10. There has been a lot of interest around this and hence I have attached some code listing to this post.

  11. govindr says:

    I think you are referring to .NET Fx 3.5. Yes this has been fixed in 3.5.

  12. Erics Blog says:

    When creating many services in SOA it’s a common scenario that you need the SAML token to flow from one

  13. Erics Blog says:

    When creating many services in SOA it’s a common scenario that you need the SAML token to flow from one

  14. Allex says:

    You mention this scenario is fixed in 3.5. Exactly what part of the code you list would not be needed when using 3.5?

  15. SK says:

    in client i don’t see the token being used?  how to i access token in the client.

  16. sgk says:

    Is there a way I can extract the SAMLAssertion from the OperationContex? )Similar to getting SecurityToken from OperationContext.Current.RequestContext.RequestMessage.Properties.Security.ProtectionToken.SecurityToken)

  17. rajesh says:

    Hi govind,

    I ‘am working on a scenario like, A service which is to issue a saml toke for his windows credentials i.e., a Service which is used as Saml token provider to a client. the client on receiveing this token, should Request any Relying Party(Service)by including the token in the message request of the client to the relying party service. Could you please let me know how can i provide a solution to this scenario?

  18. 話題の小向美奈子ストリップを隠し撮り!入念なボディチェックをすり抜けて超小型カメラで撮影した神動画がアップ中!期間限定配信の衝撃的映像を見逃すな