High-Trust SharePoint Apps, Token Lifetime and MemoryCache

In the last months I have been busy working on a project that includes high-trust on-premises SharePoint 2013 app that is accessed by many people at the same time. Each user is issued an access token that authenticates the user and points to his or her SharePoint site.

The problem that began surfacing is that by default high trust access tokens have a lifetime of only 10 minutes. So, as we cached the token in memory, after 10 minutes SharePoint would start giving 401 Unauthorized errors, due to the token being expired.

Extending the lifetime of the access token

The solution to the problem involved increasing the lifetime of the access token to 60 minutes. This is a fairly simple change in TokenHelper.cs file.

Find the IssueToken private method in TokenHelper.cs and in the "Outer Token" region, change the line that creates the JWT token to this:

MemoryCache

In addition, we have added a MemoryCache single instance in-memory application cache (available in NET 4.5). The access tokens are added to the cache with an absolute lifetime of 60 minutes, so they will expire at the same time as their lifetime in SharePoint. Once evicted from the memory cache, the access tokens are recreated with an additional 60 minutes of lifetime and stored in the cache again.

Bonus: Refactored for reusability and testing

In order to make our code simpler and more understandable, we added the caching capability as a provider, exposed by the ICachingProvider interface. The ICachingProvider interface exposes the operation for getting an object from the cache with a specific key. We wanted the code to be reusable not only for tokens but for all suitable situations.

The operation GetFromCache is a generic method that allows the user to get a typed object from the cache by providing a string key and a type. Moreover, the operation requires a fallback method. This fallback method is a generic Func<T> that returns an object of type T. The cache implementation then uses the function delegate (a lambda expression, in most cases) if the specified key is not found. By invoking the delegate, we get the object from its source (as if there were no cache) and store it in the cache with the given key.

The full CachingProvider code is displayed here. There are two auxiliary methods to add and retrieve items from the cache. In order to avoid concurrency exceptions, the access to the MemoryCache instance is protected under a lock object called padLock.

One added bonus of having an interface is that we had two implementations of the interface: CachingProvider (the normal caching service) and DummyCachingProvider (that simply bypassed the cache and returned the result of the Func delegate invocation). In this way we could disable caching by injecting the correct instance of the caching provider and also it benefitted unit testing as we could test both the cached and non-cached code paths.