Monday, March 27, 2006 6:09 PM cibrax

Client-side token cache for WCF

WCF by default maintains a cache for security tokens per channel instance (A channel is related to a contract). Therefore, it is not possible to reuse the same token for different channel instances.

Consider the following sample, a client application that consumes different services using a SAML token.

 

IHelloWorldChannel helloWorldService = factory.CreateChannel();

string response = helloWorldService.HelloWorld("John Doe");

Console.WriteLine(response);

helloWorldService = factory.CreateChannel();

response = helloWorldService.HelloWorld("John Doe 2");

Console.WriteLine(response);

factory = new ChannelFactory<IAnotherChannel>("anotherService");

helloWorldService = factory.CreateChannel();

response = helloWorldService.HelloWorld("John Doe 3");

Console.WriteLine(response);

 

In this case, I used three different channel instances and therefore a different SAML token for each service. (Each channel made an addition call to the STS in order to ask for a SAML token).

Fortunately, WCF provides a way to cache tokens outside the scope of a channel and reuse them later until they expire. 

During the course of this post, I will show the required steps to build a client-side token cache to reuse tokens obtained from a STS.  

 

First of all, I created a custom ClientCredentials class in order to return a custom SecurityTokenManager class. The SecurityTokenManager class is a kind of entry point to modify the process involved in the creation of a security token.

 

/// <summary>

/// Custom implementation

/// </summary>

class CustomClientCredentials : ClientCredentials

{

  public CustomClientCredentials()

      : base()

  {

  }

  protected CustomClientCredentials(ClientCredentials other)

     : base(other)

  {

  }

  protected override ClientCredentials CloneCore()

  {

    return new CustomClientCredentials(this);

  }

  /// <summary>

  /// Returns a custom security token manager

  /// </summary>

  /// <returns></returns>

  public override System.IdentityModel.Selectors.SecurityTokenManager CreateSecurityTokenManager()

  {

    return new CustomClientCredentialsSecurityTokenManager(this);

  }

}

 

Secondly, I declared my own SecurityTokenManager.

 

class CustomClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager

{

  private static Dictionary<Uri, CustomIssuedSecurityTokenProvider> providers = new Dictionary<Uri, CustomIssuedSecurityTokenProvider>();

  public CustomClientCredentialsSecurityTokenManager(ClientCredentials credentials)

       : base(credentials)

  {

  }

  /// <summary>

  /// Returns a custom token provider when a issued token is required

  /// </summary>

  public override System.IdentityModel.Selectors.SecurityTokenProvider CreateSecurityTokenProvider(System.IdentityModel.Selectors.SecurityTokenRequirement tokenRequirement)

  {

    if (this.IsIssuedSecurityTokenRequirement(tokenRequirement))

    {

      IssuedSecurityTokenProvider baseProvider = (IssuedSecurityTokenProvider)base.CreateSecurityTokenProvider(tokenRequirement);

      CustomIssuedSecurityTokenProvider provider = new CustomIssuedSecurityTokenProvider(baseProvider);

      return provider;

    }

    else

    {

      return base.CreateSecurityTokenProvider(tokenRequirement);

    }

  }

}

 

For this sample, I only want to cache issued tokens (Tokens obtained from a STS) and thefore I am using the IsIssuedSecurityTokenRequeriment method to determine if the channel is requesting an issued token or not. 

 

Lastly, I created a simple Cache helper and a custom token provider to reuse the issued tokens.

 

/// <summary>

/// Helper class used as cache for security tokens

/// </summary>

class TokenCache

{

   private const int DefaultTimeout = 1000;

   private static Dictionary<Uri, SecurityToken> tokens = new Dictionary<Uri, SecurityToken>();

   private static ReaderWriterLock tokenLock = new ReaderWriterLock();

   private TokenCache()

   {

   }

   public static SecurityToken GetToken(Uri endpoint)

   {

     SecurityToken token = null;

     tokenLock.AcquireReaderLock(DefaultTimeout);

     try

     {

       tokens.TryGetValue(endpoint, out token);

       return token;

     }

     finally

     {

       tokenLock.ReleaseReaderLock();

     }

   }

   public static void AddToken(Uri endpoint, SecurityToken token)

   {

     tokenLock.AcquireWriterLock(DefaultTimeout);

     try

     {

       if (tokens.ContainsKey(endpoint))

         tokens.Remove(endpoint);

         tokens.Add(endpoint, token);

     }

     finally

     {

       tokenLock.ReleaseWriterLock();

     }

   }

 }

 

 /// <summary>

 /// Custom token provider. This class keeps the tokens outside of the channel

 /// so they can be reused

 /// </summary>

 class CustomIssuedSecurityTokenProvider : IssuedSecurityTokenProvider

 {

   private IssuedSecurityTokenProvider innerProvider;

   /// <summary>

   /// Constructor

   /// </summary>

   public CustomIssuedSecurityTokenProvider(IssuedSecurityTokenProvider innerProvider)

            : base()

   {

      this.innerProvider = innerProvider;

      this.CacheIssuedTokens = innerProvider.CacheIssuedTokens;

      this.IdentityVerifier = innerProvider.IdentityVerifier;

      this.IssuedTokenRenewalThresholdPercentage = innerProvider.IssuedTokenRenewalThresholdPercentage;

      this.IssuerAddress = innerProvider.IssuerAddress;

      this.IssuerBinding = innerProvider.IssuerBinding;

      foreach (IEndpointBehavior behavior in innerProvider.IssuerChannelBehaviors)

      {

        this.IssuerChannelBehaviors.Add(behavior);

      }

      this.KeyEntropyMode = innerProvider.KeyEntropyMode;

      this.MaxIssuedTokenCachingTime = innerProvider.MaxIssuedTokenCachingTime;

      this.MessageSecurityVersion = innerProvider.MessageSecurityVersion;

      this.SecurityAlgorithmSuite = innerProvider.SecurityAlgorithmSuite;

      this.SecurityTokenSerializer = innerProvider.SecurityTokenSerializer;

      this.TargetAddress = innerProvider.TargetAddress;

      foreach (XmlElement parameter in innerProvider.TokenRequestParameters)

      {

        this.TokenRequestParameters.Add(parameter);

      }

      this.innerProvider.Open();

    }                              

 

   /// <summary>

   /// Gets the security token

   /// </summary>

   /// <param name="timeout"></param>

   /// <returns></returns>

   protected override System.IdentityModel.Tokens.SecurityToken GetTokenCore(TimeSpan timeout)

   {

     SecurityToken securityToken = null;

     if (this.CacheIssuedTokens)

     {

       securityToken = TokenCache.GetToken(this.innerProvider.IssuerAddress.Uri);

       if (securityToken == null || !IsServiceTokenTimeValid(securityToken))

       {

         securityToken = innerProvider.GetToken(timeout);

         TokenCache.AddToken(this.innerProvider.IssuerAddress.Uri, securityToken);

       }

     }

     else

     {

       securityToken = innerProvider.GetToken(timeout);

     }

     return securityToken;

   }

 

   /// <summary>

   /// Checks the token expiration.

   /// A more complex algorithm can be used here to determine whether the token is valid or not.

   /// </summary>

   private bool IsServiceTokenTimeValid(SecurityToken serviceToken)

   {

     return (DateTime.UtcNow <= serviceToken.ValidTo.ToUniversalTime());

   }

 

  ~CustomIssuedSecurityTokenProvider()

  {

    this.innerProvider.Close();

  }

 

The provider is quite simple, it caches the tokens by IssuerAddress and checks the token expiration before returning it. When the token is expired, it gets a new token calling the inner token provider.

In order to register the CustomClientCredentials class, the following configuration is required (Using the "type" attribute)

 

<behavior name="ServiceBehavior">

  <clientCredentials type="CustomClientCredentials, MyAssembly">

  </clientCredentials>

</behavior>

 

Filed under:

Comments

# SAML - STS implementation for WCF

Friday, September 08, 2006 10:40 AM by Community Blogs

I finally decided to publish a STS implementation for WCF. (It isbased onone of my previous posts, &quot;Implementing

# re: Client-side token cache for WCF

Friday, November 17, 2006 4:47 PM by John Meade

Pablo,

Perhaps you can help me to understand what appears to be a missing piece. An RST message having been sent to an STS, an RSTR message returns, conaining an issued SAML token serialized into the body of the response.  Since RST and RSTR support was pulled from WCF, I need to somehow deserialize the SAML token from the body of the response prior to caching it, but I can find nothing on how to actually do so.

What might you recommend?

# re: Client-side token cache for WCF

Thursday, August 02, 2007 8:35 PM by Matt

John,

 The "Federation" sample in the Windows SDK has some sample classes to do this in the RTM of WCF.

Pablo,

 Great post, this is really helpful.

# References

Sunday, May 11, 2008 6:49 AM by Zuker On Foundations

Some references I&#39;d like to save for potential personal future use. Client-side token cache for WCF

# re: Client-side token cache for WCF

Friday, June 13, 2008 5:58 AM by Rob

How would I go about making the token cache serializable? I'm having problems with the SecurityToken class.

Thanks

# re: Client-side token cache for WCF

Friday, June 13, 2008 11:33 AM by cibrax

Hi Rob,

You will probably have to write the SecurityToken to an string (and the opposite to read it), if you want to store it in a database or other kind of cache different from memory.

Regards,

Pablo.  

# re: Client-side token cache for WCF

Friday, March 13, 2009 3:21 PM by Katie

Using wsFederationHttpBinding, the following is the traffic flow :

[1] .Net <--  RST/RSTR --> .Net STS to obtain an SAML Token

[2] .Net --------  RST with BinarySecret  -----> My STS

[3] .Net <-----  RSTR with BinarySecret ------- My STS

.Net --- traffic signed/encrypted with SCT from [3] ---> my app

 ** at this point, I am able to decrypt/verify the message

.Net <-------  traffic signed/encrypted with SCT from [3]  ---- my app

.Net choked with cannot locate the SCT, it seems like the .Net client did not cache the SCT token it received from [3].  Is there anyway to force the Token from [3] to be cached by .Net client ?

Leave a Comment

(required) 
(required) 
(optional)
(required)