Reading Katana Cookie Authentication Middleware’s Cookie from FormsAuthenticationModule

I saw a question in stackoverflow about using the cookie created by FormsAuthenticationModule (FAM) from the Katana Cookie Authentication Middleware. I thought it was a one-off question. But then, here is one more question, similar, but it is about reading the cookie created by CAM from FAM. Though we cannot change FAM’s behavior, it is technically possible to write an HTTP module to read the ticket. So, thought of writing some code to illustrate how to read the CAM cookie.

So, we start off with this empty module.

public class MyHttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.AuthenticateRequest += OnApplicationAuthenticateRequest;
    }

    private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
    {
        // We read the CAM cookie here - steps below.
    }

    public void Dispose() { }
}

First, we get the cookie using the default name of “.AspNet.ApplicationCookie”. We then decode the Base64 URL encoded string to get the proper Base64 encoded string. Then, we convert it to an array of byte.

var request = HttpContext.Current.Request;
var cookie = request.Cookies.Get(".AspNet.ApplicationCookie");
var ticket = cookie.Value;
ticket = ticket.Replace('-', '+').Replace('_', '/');

var padding = 3 - ((ticket.Length + 3) % 4);
if (padding != 0)
    ticket = ticket + new string('=', padding);
            
var bytes = Convert.FromBase64String(ticket);

At this point, what we have is the protected ticket. Ticket is protected using the data protector applicable to the host. In case of IIS (ASP.NET), machine key is used. So, we can use the machine key API to unprotect. To unprotect, we need the purpose. CAM uses the purpose strings, as shown below.

var bytes = Convert.FromBase64String(ticket);

bytes = System.Web.Security.MachineKey.Unprotect(bytes,
          "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware",
                    "ApplicationCookie", "v1");

It goes without saying that, in order to unprotect like this, the machine key used by the web app running CAM and the web app running this module must share the decryption and validation keys. So, web.config in both apps must have the same keys and must be something like this. Of course, you need to be replacing the values below with your own keys.

<system.web>
    <machineKey validationKey="C435FCF3C6BC11FC9D54093AA3F64EADE882D0B434FD25A2FCBC7068B9F2E817" 
                decryptionKey="FA9217D420AEB67377A079A68163A3164802DB79CA76AFA8AC8BDA7CAA4F93D4" 
                validation="HMACSHA256"
                decryption="AES" />
    ...
</system.web>

With that done, we need to unzip the ticket and use BinaryReader to read the contents of the ticket. In the code below, I’m ignoring the name claim type and role claim type and assuming the default ones are used. To minimize the ticket size, CAM uses a placeholder of “\0” to store default values. For example, to store a name claim, type will not store the lengthy string of http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name but just “\0”. Similarly, the place holder is used for the value type of string and the issuer “LOCAL AUTHORITY”. Finally, we use the identity to create a claims principal and set it in T.CP and HTTP context.

using (var memory = new MemoryStream(bytes))
{
    using (var compression = new GZipStream(memory, CompressionMode.Decompress))
    {
        using (var reader = new BinaryReader(compression))
        {
            reader.ReadInt32(); // Ignoring version here
            string authenticationType = reader.ReadString();
            reader.ReadString(); // Ignoring the default name claim type
            reader.ReadString(); // Ignoring the default role claim type

            int count = reader.ReadInt32(); // count of claims in the ticket

            var claims = new Claim[count];
            for (int index = 0; index != count; ++index)
            {
                string type = reader.ReadString();
                type = type == "\0" ? ClaimTypes.Name : type;

                string value = reader.ReadString();

                string valueType = reader.ReadString();
                valueType = valueType == "\0" ? 
                                "http://www.w3.org/2001/XMLSchema#string" : valueType;

                string issuer = reader.ReadString();
                issuer = issuer == "\0" ? "LOCAL AUTHORITY" : issuer;

                string originalIssuer = reader.ReadString();
                originalIssuer = originalIssuer == "\0" ? issuer : originalIssuer;

                claims[index] = new Claim(type, value, valueType, issuer, originalIssuer);
            }

            var identity = new ClaimsIdentity(claims, authenticationType, 
                                                  ClaimTypes.Name, ClaimTypes.Role);

            var principal = new ClaimsPrincipal(identity);
            System.Threading.Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
                HttpContext.Current.User = principal;
        }
    }
}

The last step is to hook our custom module into the IIS pipeline.

<system.webServer>
    ...
    <modules>
        <add name="MyHttpModule" type="namespace.MyHttpModule, assemblyname"/>
    </modules>
</system.webServer>
Advertisements

7 thoughts on “Reading Katana Cookie Authentication Middleware’s Cookie from FormsAuthenticationModule

  1. Your post was absolutely brilliant and insightful. Thanks a lot for saving me a load of time.

  2. Another way to get AuthenticationTicket from bytes[] is:

    var unprotectedBytes = MachineKey.Unprotect(bytes,”Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware”,”ApplicationCookie”, “v1”);
    var ticketDeserialized = new TicketSerializer().Deserialize(unprotectedBytes);

  3. Not so long ago I wrote an article where I described how to build a shared layer of authentication between .NET4.5 ASP.NET Identity and .NET4.0 Forms applications (http://lowleveldesign.wordpress.com/2014/08/03/common-authentication-authorization-between-net40-net45/). I was creating the forms cookie in the ASP.NET Identity and wrote an http module to extract claims data in the forms app. I must say that your approach is MUCH better as you are using only one cookie and you do not need to change the code in the new application. I have only one thing to add for people following your path, but having to deal with .NET4.0 – remember to add compatibilityMode=”Framework20SP2″ in your machineKey definition. Otherwise old applications won’t be able to decrypt the shared authentication cookies. Thanks again for a nice post!

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