Caching Tokens to Avoid Calls to the STS using the Geneva Framework

There is a lot of talk in the Geneva forum about caching and avoiding unnecessary round trips to the STS when calling downstream Web services. After all of it, I thought that reusing the same ChannelFactory<T> object would result in a single call to the STS for a token and that subsequent calls made with that factory would reuse the SAML token. I thought that a service type like this would suffice:

It turns out this is not correct, however. Every time a channel is created, invoking operations on that proxy will result in a call to the STS for a security token (which is really a few service calls for policy and whatnot). At first, I thought that the way to avoid this was to reuse the channel and not the channel factory. This may work if the channel remains open; however, if it is closed, it will get disposed and subsequent calls will fail. I don't know if it is a bad practice in this case to keep the channel open continuously, but, as a rule, holding onto resources for longer than absolutely necessary is to be avoided. Working off this assumption, I sought to find an alternative that would avoid the extra calls to the STS without having to hold onto an open channel object.

A couple of colleagues told me that I needed to cache the SAML token outside of the channel and retrieve them from there by adding an interceptor to the WCF pipeline. They referred me to Cibrax's blog, where he describes the process. After reading that article and Eric Quist's post, I understood that hooking into WCF to replace the default SAML token provider with one that does caching requires the definition of three classes:

CacheClientCredentials - Plumbing

CacheClientCredentialsSecurityTokenManager - Plumbing

CacheSecurityTokenProvider - Actual meat

The code that Cibrax and Eric wrote is really good, and it provides an excellent starting point for doing this in Geneva; however, I needed to tweak their code a bit to get it to work with this new framework. Specifically, I had to make the following changes:

CacheClientCredentials

Must inherit from FederatedClientCredentials

Must override CloneCore and return a new CacheClientCredentials object

May provide a static method for configuring a channel factory object to use CacheClientCredentials (similarly to FederatedClientCredentials - in beta 1 at least)

// Only add the token to the cache if caching has been turned on in web/app.config.

if (CacheIssuedTokens)

{

TokenCacheHelper.AddToken(cacheKey, securityToken);

}

}

return securityToken;

}

privatestaticbool IsSecurityTokenExpired(SecurityToken serviceToken)

{

returnDateTime.UtcNow >= serviceToken.ValidTo.ToUniversalTime();

}

~CacheSecurityTokenProvider()

{

if (!disposed)

((IDisposable)this).Dispose();

}

voidIDisposable.Dispose()

{

innerProvider.Close();

disposed = true;

}

}

Conclusion

Now, having said all this and listing a bunch of code, let's step back for a moment. Caching of SAML tokens is a must to avoid extra round trips to the STS. Doing so requires a bunch of plumbing and everyone will absolutely require this functionality. So,why are we doing this? Geneva is a framework, and as such, IMO, it should be proving things like caching of SAML tokens for us. It has been said by the Geneva team that support for caching of tokens is a top priority, so hopefully the code above will be obsolete very soon.

Categories:

Tags:

There is a lot of talk in the Geneva forum about caching and avoiding unnecessary round trips to the STS when calling downstream Web services. After all of it, I thought that reusing the same ChannelFactory<T> object would result in a single call to the STS for a token and that subsequent calls made with that factory would reuse the SAML token. I thought that a service type like this would suffice:\n

It turns out this is not correct, however. Every time a channel is created, invoking operations on that proxy will result in a call to the STS for a security token (which is really a few service calls for policy and whatnot). At first, I thought that the way to avoid this was to reuse the channel and not the channel factory. This may work if the channel remains open; however, if it is closed, it will get disposed and subsequent calls will fail. I don't know if it is a bad practice in this case to keep the channel open continuously, but, as a rule, holding onto resources for longer than absolutely necessary is to be avoided. Working off this assumption, I sought to find an alternative that would avoid the extra calls to the STS without having to hold onto an open channel object.\n

A couple of colleagues told me that I needed to cache the SAML token outside of the channel and retrieve them from there by adding an interceptor to the WCF pipeline. They referred me to Cibrax's blog, where he describes the process. After reading that article and Eric Quist's post, I understood that hooking into WCF to replace the default SAML token provider with one that does caching requires the definition of three classes:\n

CacheClientCredentials - Plumbing \n

CacheClientCredentialsSecurityTokenManager - Plumbing \n

CacheSecurityTokenProvider - Actual meat\n

The code that Cibrax and Eric wrote is really good, and it provides an excellent starting point for doing this in Geneva; however, I needed to tweak their code a bit to get it to work with this new framework. Specifically, I had to make the following changes:\n

CacheClientCredentials\n\t

Must inherit from FederatedClientCredentials\n\t\t

Must override CloneCore and return a new CacheClientCredentials object\n

May provide a static method for configuring a channel factory object to use CacheClientCredentials (similarly to FederatedClientCredentials - in beta 1 at least)\n

// Only add the token to the cache if caching has been turned on in web/app.config.

if (CacheIssuedTokens)

{

TokenCacheHelper.AddToken(cacheKey, securityToken);

}

}

return securityToken;

}

privatestaticbool IsSecurityTokenExpired(SecurityToken serviceToken)

{

returnDateTime.UtcNow >= serviceToken.ValidTo.ToUniversalTime();

}

~CacheSecurityTokenProvider()

{

if (!disposed)

((IDisposable)this).Dispose();

}

voidIDisposable.Dispose()

{

innerProvider.Close();

disposed = true;

}

}

\n

Conclusion\n

Now, having said all this and listing a bunch of code, let's step back for a moment. Caching of SAML tokens is a must to avoid extra round trips to the STS. Doing so requires a bunch of plumbing and everyone will absolutely require this functionality. So,\n\t\twhy are we doing this? Geneva is a framework, and as such, IMO, it should be proving things like caching of SAML tokens for us. It has been said by the Geneva team that support for caching of tokens is a top priority, so hopefully the code above will be obsolete very soon.