SCT Options - Using a database to cache SCT's in a farm, Volume 2

Last entry I discussed how to extend the SCT manager, and MRUSecurityTokenCache to use a database to persist tokens to share SCT's across service instances within a web farm.  I left the details of token serialization not implemented however.  This blog I'll dig into that with a generic method of serializing and deserializing tokens.  I'd like to note that it would be more efficient generally if you have control over the base token to use a more efficient mechanism to serialize the token.

Tokens are unfortunately not serializable.  There are a few reasons this design choice was made - the downer is that efficient token serialization is not as simple as it could be.  There are two tokens we need to be concerned with - the first is the SCT itself, and the second is the "base token" of the SCT.  I discussed the base token in a previous blog but realized that I wasn't particularly clear about where it came from.  When estabilshing an SCT, a token is provided by the client to prove its authenticity and estabilish identity before the SCT will be issued.  In the case of the secure conversation example, a UsernameToken is used for client credentials for the SCT request.  If the credentials are accepted, then this token becomes the "base" token of the SCT.  The SCT itself is generally very lightwieght, while the base token typically contains identity and perhaps authorization related information for the authenticated client.

So the bottom line is that we need to serialize both of these.  The approach I took assumes that generally tokens that are not SCT's are stateless - i.e., they can be reconstituted based upon the information contained within the token xml, and perhaps a secret know on each service - for instance, the private key of a public cert installed on all servers in the farm, or a symmetric key shared in the configuraiton accross all servers in the farm.  The method we are going to work on is one added to the DistributedTokenCache described in my last entry:

  provtected virtual void SaveToStore(SecurityToken token)       {               … persist the token       } I'm going to describe how I implemented this for SCT's. One could generalize this for other tokens, but I haven't yet figuredout a valid use case for this need. For the base tokens, I'm going to use a method every token must implement to serialize itself to xml, GetXml, and to deserialize the token on reload using the appropriate token manager's LoadTokenFromXml method. For the SCT, define a simple class to contain the relevant information from the SCT. For my purposes, I defined thefollowing as sufficient, altthough there are additional items you could push off - for instance, if you were using the"AppliesTo" value for some of your validation, you would want to add that information to this class: [Serializable] private class SCTData

{

public byte[] KeyBytes;

public string TokenIssuer;

public DateTime Created;

public DateTime Expires;

public string BaseTokenType;

public string SerializedBaseToken;

}

I set the relevant data values from the SCT being seriailzed, (Created and Expired come from Lifetime which is not

serializable). SerializedBaseToken is a string containing the XML retrieved from the base token using GetXml. I then

serialize this class into a byte stream and store it to the database along with the associated identifier (used to retrieve the

entry on deserialization). I also added a column where I put the current datetime so I can clean up the store later.

My first thought was to use Expires, but unfortunately Expires is a few thousand years in the future by default.

I created a somewhat artficial test case to see if this logic worked by skipping placing the token in the cache when first

added and expected to see everything run beautifully.. Well it didn't. I got a SecurityFault and a decryption error. I

started digging, and I found out that the SCT wasn't in fact complete when it was added to the cache, so I was serializing

an incomplete token to the cache database, which was then of course incomplete when de-serialized on the next

request. The base token wasn't set, nor the TokenIssuer (as well as a few other properties). Next entry I'll discuss

how I fixed this particular issue (and two others) to get the cache running.