Skip to content

Server-side session token caching in WIF and Thinktecture IdentityModel

February 21, 2013

Once a user has been authenticated session tokens are emitted by the SAM as cookies. These session cookies are fairly large (given that they contain claims) and so it is desirable to optimize them to a smaller size (especially for browsers like Safari which have issues with large cookies).

In order to reduce session token size, WIF supports server-side session security token caching. I already discussed how to enable this feature here. Additionally, a CacheSessionsOnServer convenience function has been added to Thinktecture IdentityModel (which must be invoked from Init in global.asax). More on this API in a bit.

The default implementation of the server-side cache (provided by WIF) simply maintains the tokens in-memory on the web server and thus is a poor choice for production given the propensity for application domain recycles and the use of server farms. To then further address this issue, WIF supports custom server-side cache implementations by deriving from SessionSecurityTokenCache. Unfortunately this cache base class is meant for both passive scenarios (browser-based, WS-Federation) and active scenarios (programmatic, WS-Trust) and as such is slightly more complex than I’d like it to be.

If your application is only a browser-based application then instead of implementing the full-fledged WIF cache you can instead implement a cache derived from the PassiveRepositorySessionSecurityTokenCache base class provided in Thinktecture IdentityModel. It uses a combination of the built-in in-memory cache as well as a pluggable persistence layer to support longer-term and cross-machine caching (in a database or distributed in-memory cache, for example). This implementation is specifically focused on the web-based scenarios (as opposed to the WCF-based scenarios), thus the name Passive. To that end, there are some methods of the WIF session security token cache base class that are not implemented due to the lack of use for web-based scenarios.

The PassiveRepositorySessionSecurityTokenCache models token via the ITokenCacheRepository interface. The interface definition is:

public interface ITokenCacheRepository
{
    void AddOrUpdate(TokenCacheItem item);
    TokenCacheItem Get(string key);
    void Remove(string key);
    void RemoveAllBefore(DateTime date);
}

 public class TokenCacheItem
{
    [Key]
    public string Key { get; set; }
    public DateTime Expires { get; set; }
    public byte[] Token { get; set; }
}

TokenCacheItem has a string primary key, an expiration and the token itself (as a byte[]). The repository models adding or updating the cache item, getting a cache item, removing a cache item and removing stale cache items past a particular date.

There is no standard implementation of the token cache repository in IdentityModel. This implementation is left to the application developer. As an example, though, if you were to use entity framework as the repository then it might look like this:

public class EFTokenCacheDataContext : DbContext
{
    public EFTokenCacheDataContext()
        :base("name=TokenCache")
    {
    }

    public DbSet<TokenCacheItem> Tokens { get; set; }
}

public class EFTokenCacheRepository : ITokenCacheRepository
{
    public void AddOrUpdate(TokenCacheItem item)
    {
        using (EFTokenCacheDataContext db = new EFTokenCacheDataContext())
        {
            DbSet<TokenCacheItem> items = db.Set<TokenCacheItem>();
            var dbItem = items.Find(item.Key);
            if (dbItem == null)
            {
                dbItem = new TokenCacheItem();
                dbItem.Key = item.Key;
                items.Add(dbItem);
            }
            dbItem.Token = item.Token;
            dbItem.Expires = item.Expires;
            db.SaveChanges();
        }
    }

    public TokenCacheItem Get(string key)
    {
        using (EFTokenCacheDataContext db = new EFTokenCacheDataContext())
        {
            DbSet<TokenCacheItem> items = db.Set<TokenCacheItem>();
            return items.Find(key);
        }
    }

    public void Remove(string key)
    {
        using (EFTokenCacheDataContext db = new EFTokenCacheDataContext())
        {
            DbSet<TokenCacheItem> items = db.Set<TokenCacheItem>();
            var item = items.Find(key);
            if (item != null)
            {
                items.Remove(item);
                db.SaveChanges();
            }
        }
    }

    public void RemoveAllBefore(DateTime date)
    {
        using (EFTokenCacheDataContext db = new EFTokenCacheDataContext())
        {
            DbSet<TokenCacheItem> items = db.Set<TokenCacheItem>();
            var query =
                from item in items
                where item.Expires <= date
                select item;
            foreach (var item in query)
            {
                items.Remove(item);
            }
            db.SaveChanges();
        }
    }
}

Given the nature of the session security token cache in WIF, complex initialization can’t be performed in web.config. Instead configuration will need to be done programmatically. Configuring the PassiveRepositorySessionSecurityTokenCache can be done in code in global.asax:

protected void Application_Start()
{
    PassiveSessionConfiguration.ConfigureSessionCache(new EFTokenCacheRepository());
}

The prior configuration only configures which cache and repository to use on the server-side. It does not configure the SAM to use the smaller cookie format. To configure the smaller cookie format the IsReferenceMode flag must be set to true on the SAM. There is no web.config setting for this, so it needs to be set at runtime and has been provided via a helper called method in the IdentityModel library via the API CacheSessionsOnServer on the PassiveModuleConfiguration class. Unfortunately given the nature of the SAM as a http module this needs to be configured on each and every instance which is only possible from the Init method in global.asax, so this code will need to be added to the hosting application:

public override void Init()
{
    PassiveModuleConfiguration.CacheSessionsOnServer();
}

Init is invoked each time a http module is created and thus is where instance properties of the http modules can be modified.

I have a sample that uses EF as the backing store for the cache here.

Happy token caching. :)

27 Comments leave one →
  1. RonyK permalink
    February 26, 2013 10:28 am

    Hi, Thanks for this article, this is exactly what I need. What version of the Thinktecture IdentityModel contains the CacheSessionsOnServer convenience function, the PassiveRepositorySessionSecurityTokenCache and the ITokenCacheRepository interface?
    I installed the latest ThinkTecture.IdentityModel Nuget (2.3.0) And it doesn’t seem to contain them.

  2. March 20, 2013 9:32 pm

    Thanks for the article! Just curious, do you have a working example of this? I am trying to implement this and I get SessionAuthenticationModule NRE … Do I need to set up the caches in the web.config as well? Perhaps I am not authenticated correctly.. I was basing my implementation on the Pluralsight training that Dominick had done… I am trying to create a DB persistence for load balancing.

    Thanks so much!

  3. Sam permalink
    September 10, 2013 1:32 pm

    in the sample code “SessionSecurityTokenCache” , in the Init() method of the global.asax, there is a call to the “PassiveModuleConfiguration.SuppressLoginRedirectsForApiCalls()”, is this call needed?

    Thank you

    • September 10, 2013 2:51 pm

      Only if you are using Ajax calls to your app and want 401 instead of 302 when the calls are unauthorized. But that feature is unrelated to the token cache.

  4. Sam permalink
    September 10, 2013 1:53 pm

    from looking at the example, the setup in the “Init()” method of the “CacheSessionsOnServer()” method in the global.asax and the implementation of the class “EFTokenCacheRepository” would all be done in the “Thinktecture IdentityServer”, correct?

    • September 10, 2013 2:51 pm

      Session token caching has nothing to do with IdentityServer.

      • Sam permalink
        September 10, 2013 2:55 pm

        so if i wanted to do the session token caching, i could essentially set that up in the “IdentityServer” and have the “EFTokenCacheRepository” class code reside in the “IdentityServer” solution and use it there, even though it has nothing to do with the IdentityServer itself but could use it there in the solution?

        • September 10, 2013 7:05 pm

          Token caching is there to reduce the cookie size, that’s it. So whatever project is using the WIF SAM for managing a user’s login session, that’s where you could do this token caching.

          A RP could use it (since it’s probably using WIF), but you could also do this in IdentityServer as well because it uses the SAM to track user login sessions.

      • Sam permalink
        September 10, 2013 5:35 pm

        based on the sample code, how does the token get saved into the database? is it automatically saved after doing the initial necessary setups?

        • September 10, 2013 7:05 pm

          The token cache repository (your code) is the bridge between the SAM in WIF and your database/cache. IdentityModel is what glues them all together.

  5. September 11, 2013 9:34 am

    I’m using this approach to cache tokens generated from a WSFederation sign in. All works well, until it comes to sign out – I was expecting the ‘Remove’ method to be called when my RP-STS calls back to the RP with the sign out message, but that never happens. Sign out works correctly in all other respect – the reference cookie is destroyed – however I’d like to be able to terminate my session in the datastore at that point too.

    • September 11, 2013 9:53 am

      The SAM should be invoking it when it revokes the cookie. I’ll have to try to repro it. If this remains an issue, I’d suggest opening an issue on github so we can track it.

  6. Piotr permalink
    February 27, 2014 1:14 pm

    I’m trying to implement token caching in our environment that consists of two RPs (RP1 and RP2) and Thinktecture STS. I’m using SQL server DB as the persistent cache.

    Caching works great when user remains in RP1. However, once user navigates to the RP2, i see two big FedAuth cookies created.

    Two strange things:
    1. Even when in RP2, I still see activity happening in the token cache DB. It just looks like it’s being ignored and all claims are written to FedAuth cookies.
    2. If user starts in RP2, the caching works too but when user navigates to RP1, then caching seems to break and I see the two big FedAuth cookies again.

    It seems to me that there is trouble trying to cross between RPs. Is that by design?

    thanks

    • February 28, 2014 8:25 am

      Each RP issues its own cookie when the user is logged in, so you’d need to do caching in each.

      • Piotr permalink
        June 24, 2014 11:19 am

        I have a follow up question to this (yea, it’s been a while).

        Could you explain a bit more how the transfer of cached token happen between RP1 and RP2?

        What I’m trying to understand is how RP2 (receiving RP) can read the cached token from RP1 since all the browser has is a reference to a cache record somewhere. For example, if user is on RP1 which caches the token, then clicks on a link that takes him to RP2 (doesn’t implement caching), does RP1 somehow intercept this request and “uncaches” the token before moving on to RP2?

        Thanks.

        • June 24, 2014 6:26 pm

          It doesn’t. When you so SSO each RP goes to the STS, the STS issues separate tokens to each RP and then the RP issues its own cookie.

  7. April 2, 2015 4:56 am

    Hi, I made a Redis implementation. Expiration is automatically handled and it doesn’t pollute your database. https://github.com/guillaume-fr/RedisTokenCache also available as NuGet package.

  8. judowalker permalink
    June 5, 2015 9:47 pm

    Hi, I’m using the OWIN cookie-based authentication middleware along with WsFederation authentication middleware and was wondering if you have something similar to PassiveSessionConfiguration.ConfigureSessionCache for OWIN? I’m surprised that they don’t already have something like this implemented.

    • June 11, 2015 9:30 am

      No, sorry. In general the cookie size is smaller with Katana (tho, still not as small as they could be).

  9. Oleg permalink
    August 19, 2015 5:41 am

    Hi! I’m trying to implement a claims based authentication in existing ASP NET app. Could you tell me where is the proper place for PassiveSessionConfiguration.ConfigureSessionCache in HttpModule?
    Thanks!

  10. December 31, 2015 1:27 am

    Hi Brockallen

    As you stated :
    If we use OWIN Cookie based authentication no caching is required.

    My Question& Requirement:
    My requirement is to use Redis cache at identityserver for tokens..

    Is runtime caching(IdSrv3 uses) enough to serve high concurrency???

    Note: Correct me if I am not heading to right direction..

    • January 2, 2016 9:04 am

      For IdentityServer questions, you should use the issue tracker on github. Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: