The Anatomy of Forms Authentication
Asp.Net’s Forms Authentication has been enthusiastically accepted and widely used since it was first introduced in .Net 1.0. Forms Authentication is very easy to use and provides a great deal of flexibility. In this aricle, I will attempt explain in “gory” technical details how this technology works. In addition, I will provide a compatible implementation of the Forms Authentication classes written in Java and a completely managed implementation written in C# (Asp.Net’s implementation relies heavily on interop via pinvoke). The C# code is available via the link on the page. If you would like the Java code please email me firstname.lastname@example.org.
Note: This article only examines the “cookie based” implementation of Forms Authentication. Asp.Net does provide an alternative implementation that allows for the ticket to be transported as a query string value. Both implementations are nearly identical and therefor the information contained herein is applicable.
Much of the information contained is either undocumented by MS or hidden really well. I searched for days on end and could not find a single website that has documented this information. Maybe it’s out there but I couldn’t find it!
At the heart of Forms Authentication are two main classes. They are FormsAuthentication and FormsAuthenticationTicket (both are defined in System.Web.Security). The former is responsible for encrypting, decrypting, and formatting the FormsAuthenticationTicket for transport. The latter holds all ticket information.
When an unauthenticated user attempts to access a Forms Authentication protected web site the following process occurs (for this demonstration we will assume each step is completed successfully):
1) The Forms Authentication HttpModule redirects the user to the logon page
2) User enter user name and password
3) A FormsAuthenticationTicket is constructed based on configuration information
4) The FormsAuthentication.Encrypt() method is called and returns a hex string containing the user’s ticket.
5) The encrypted ticket string is then placed into a cookie and added to the user’s cookie collection.
A closer look at FormsAuthentication.Encrypt():
Now let’s get down to the internals of how a FormsAuthenticationTicket is made ready for being stuffed into a cookie.
FormsAuthentication.Encrypt() is a static method of the FormsAuthentication class. This methods primary function is to convert the ticket into a binary blob (byte array), digitally sign it, encrypt it, and format it in such a way that it can easily be place into a cookie.
Let’s take this a step at a time…
Create a binary blob from the ticket:
In Microsoft’s implementation the conversion of the ticket into a binary blob is “black boxed” in a method named “CookieAuthContructTicket” (located in the native dll “webengine.dll”). One can assume this was done to keep the prying eyes of guys like me out of their business! In any event, after many hours of work, and considerable effort, I was able to figure out exactly what was going on in that dll.
Before I explain precisely what is going on, it is important to review what information the ticket blob contains. The ticket blob is constructed of the formatted property values defined in the FormsAuthencticationTicket class.
Name String User name
UserData String Any extra user information
CookiePath String The path the cookie is valid for ie. /AppPath
Expriation DateTime The time at which the ticket is invalid
IssueDate DateTime The time at which the ticket was issued
IsPersistent bool Indicates if the ticket is a persistent ticket
Version int The ticket version
Now that we have that info let’s take a look at how these properties are formatted and combined into a binary blob.
1) Each string value is converted to a byte array (represented as a Unicode string).
2) Each DateTime value is converted into the equivalent “FileTime” (represented as a long value) and converted to a byte array.
3) IsPersistent and Version are cast to their equivalent byte representation
After these properties are converted to their byte array equivalents, the final ticket blob is constructed as described below.
1) Eight random bytes are generated and become the first 8 bytes of the final blob. This is done to ensure each encrypted ticket will differ even if the data is the same.
2) The “Version” byte is added at the 8th position of the blob.
3) Followed by the "Name" bytes
4) Followed by a 2 byte delimiter (both bytes are 0’s)
5) Followed by the "IssueDate" bytes
6) Followed by the "IsPersistent" byte
7) Followed by the “Expires” bytes
8) Followed by the “UserData” bytes
9) Another two byte delimiter is placed directly after the “UserData” bytes
10) Followed by the “CookiePath” bytes
11) Last but not least a final delimiter (another 0) is appended
Below is a detailed diagram of the final ticket blob….
<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter" /><formulas><f eqn="if lineDrawn pixelLineWidth 0" /><f eqn="sum @0 1 0" /><f eqn="sum 0 0 @1" /><f eqn="prod @2 1 2" /><f eqn="prod @3 21600 pixelWidth" /><f eqn="prod @3 21600 pixelHeight" /><f eqn="sum @0 0 1" /><f eqn="prod @6 1 2" /><f eqn="prod @7 21600 pixelWidth" /><f eqn="sum @8 21600 0" /><f eqn="prod @7 21600 pixelHeight" /><f eqn="sum @10 21600 0" /></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /><lock v:ext="edit" aspectratio="t" />
Digitally sign the ticket:
Note: This is another place in the process Microsoft chose to use pinvoke and a native dll.
After the ticket blob has been constructed it must be digitally signed to ensure the information is not tampered with. The digital signature is an HMAC (Hashed Message Authentication Code) hash of the ticket blob. Depending on the configuration either MD5 or SHA1 is used for the HMAC. In managed code these classes are implemented as HMACMD5 and HMACSHA1. The digital signature is a 20 byte hash of the data. This hash is appended to the end of ticket blob.
Encrypting the ticket and signature:
At this point in the process we have a binary blob (byte array) containing all of the ticket information as well as the signature of this data. Before transporting this information it must be encrypted. Depending on the configuration the information is either encrypted using AES (implemented as RijndaelManaged in managed code) or 3Des (implemented as TripleDesCryptoServiceProvider in managed code).
Formatting the encrypted ticket and signature:
Given that the encrypted ticket will need to be inserted into a cookie, the final step is to create a string from the data blob. There are a couple of choices on how you can go about doing this. Many people convert byte arrays to Base64 strings and transport them that way. Microsoft went another, equally valid route, and chose to encode the data as a hex string.
Stuffing the ticket into a Cookie:
With our hex string in hand we are set to complete the process. The final step is to simply place the hex string into a cookie and send it off to the user’s browser.
At this point the user has been given the “keys to the kingdom”. Each time the user attempts to visit a site for which his ticket is configured, he will be given access without any work on his part. From a technical a standpoint what happens is straight forward. When a user visits a site or pages the forms HttpModule intercepts the request, retrieves the cookie, grabs the ticket from the cookie, and does the reverse of what I have outlined here. If the decryption succeeds (which will only happen if the same key is used for decryption) and the signature is valid then the user is given access to the page or site.