Protecting Cookies: Once and For All

Every once in a while you run into a situation where you need to temporarily store data for a user in a web app. You typically have two options here – either store server-side or put the data into a cookie (if size permits).

When you need web farm compatibility in addition – things become a little bit more complicated because the data needs to be available on all nodes. In my case I went for a cookie – but I had some requirements

  • Cookie must be protected from eavesdropping (sent only over SSL) and client script
  • Cookie must be encrypted and signed to be protected from tampering with
  • Cookie might become bigger than 4KB – some sort of overflow mechanism would be nice

I really didn’t want to implement another cookie protection mechanism – this feels wrong and btw can go wrong as well.

WIF to the rescue. The session management feature already implements the above requirements but is built around de/serializing IClaimsPrincipals into cookies and back. But if you go one level deeper you will find the CookieHandler and CookieTransform classes which contain all the needed functionality.

public class ProtectedCookie
{
   
private List<CookieTransform
> _transforms;
   
private ChunkedCookieHandler _handler = new ChunkedCookieHandler
();

   
// DPAPI protection (single server)
    public
ProtectedCookie()
    {
        _transforms =
new List<CookieTransform
>
            {
               
new DeflateCookieTransform
(),
               
new ProtectedDataCookieTransform
()
            };
    }

   
// RSA protection (load balanced)
    public ProtectedCookie(X509Certificate2
protectionCertificate)
    {
        _transforms =
new List<CookieTransform
>
            {
               
new DeflateCookieTransform
(),
               
new RsaSignatureCookieTransform
(protectionCertificate),
               
new RsaEncryptionCookieTransform
(protectionCertificate)
            };
    }

   
// custom transform pipeline
    public ProtectedCookie(List<CookieTransform
> transforms)
    {
        _transforms = transforms;
    }

   
public void Write(string name, string value, DateTime
expirationTime)
    {
       
byte
[] encodedBytes = EncodeCookieValue(value);

        _handler.Write(encodedBytes, name, expirationTime);
    }

   
public void Write(string name, string value, DateTime expirationTime,
string domain, string
path)
    {
       
byte
[] encodedBytes = EncodeCookieValue(value);

        _handler.Write(encodedBytes,
name,
path,
domain,
expirationTime,
true,
true,
HttpContext
.Current);
    }

   
public string Read(string
name)
    {
       
var
bytes = _handler.Read(name);

       
if (bytes == null
|| bytes.Length == 0)
        {
           
return null
;
        }

       
return
DecodeCookieValue(bytes);
    }

   
public void Delete(string
name)
    {
        _handler.Delete(name);
    }

   
protected virtual byte[] EncodeCookieValue(string
value)
    {
       
var bytes = Encoding
.UTF8.GetBytes(value);
       
byte
[] buffer = bytes;

       
foreach (var transform in
_transforms)
        {
            buffer = transform.Encode(buffer);
        }

       
return
buffer;
    }

   
protected virtual string DecodeCookieValue(byte
[] bytes)
    {
       
var
buffer = bytes;

       
for (int
i = _transforms.Count; i > 0; i—)
        {
            buffer = _transforms[i - 1].Decode(buffer);
        }

       
return Encoding.UTF8.GetString(buffer);
    }
}

HTH

This entry was posted in ASP.NET, IdentityModel. Bookmark the permalink.

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