Dissecting Forms AuthenticationBy Scott Mitchell
If you've ever used ASP.NET to create a website that requires that users login to view certain pages or to have access to particular features, then you've no doubt examined ASP.NET's forms-based authentication scheme. ASP.NET's forms-based authentication allows you to quickly and easily build a website that authenticates users through a forms-based approach. Namely, to identify themselves, a user will enter their credentials in a Web Form. (Credentials could be anything, really, but for websites they are typically just a username and password. However, some websites require more involved credentials, such as a username, password, and PIN.) After a user enters their credentials, they are "logged in" to the site.
There have been plenty of articles here on 4Guys as well as across the Web that discuss forms-based authentication in
ASP.NET. For more information see Darren Neimke's articles Using
Forms Authentication in ASP.NET and Role-Based
Authorization with Forms Authentication; also check out the User
Authentication section here on 4Guys. However, most of these articles focus simply on how to implement forms-based
authentication, topics such as what settings are required in
Web.config and the code necessary for the
forms-based login page.
With this article we will not be looking at how to implement forms-based authentication; rather, we will peel back the layers of forms-based authentication and examine what's really happening when a user is "authenticated." If you've ever wondered how, exactly, a user's authentication status is remembered as they visit various pages on the site, or how ASP.NET protects against nefarious users from circumventing the authentication process and "faking" a successful login without valid credentials, read on to learn more!
The Forms-Based Authentication Workflow
Before we begin our dissection of ASP.NET's forms-based authentication, let's take a brief moment to examine the forms-based authentication workflow from both the end user's perspective and the page developer's perspective. While reading the end user and page developer point of views be sure to keep in mind that authentication is the process of identifying a user, nothing more, nothing less. So forms-based authentication is simply identifying a visitor and doing so using a forms-based mechanism. (That is, the user enters their identifying credentials through a form on a web page.)
From the end user's point of view, when visiting a site they are typically allowed to login by entering their name and password. Alternatively, if they attempt to visit a page that they need to be authenticated in order to access, they'll automatically be sent to the login page. At the login page, our end user will enter her credentials - typically a username and password - and click a button. Assuming their credentials are valid, they'll then be "logged on" to the site. Once our end user is logged on, she can visit any number of pages on the site and be remembered - that is, she need not re-enter her credentials unless she's closed her browser or explicitly logged out. (Some financial sites or sites that are commonly visited through public terminals will automatically log out a user after a period of inactivity.)
From our view as the page developer, implementing forms-based authentication requires setting up the
so that in the
<authentication> setting the
<forms> element is used. This
<forms> element contains information as to the URL of the login page along with information on the
user's authentication ticket (more on this later). For a complete listing of properties that can be set through
<forms> element refer to the technical
documentation. In addition to the
Web.config settings, we'll also need to create a login page. This
login page needs to prompt the user for their credentials and, on postback, verify if the credentials are valid. If
the credentials are indeed valid, we "log on" the user by calling the
method or the
SetAuthCookie() method is what actually logs on the user to the site.
if your site is using authorization and a user attempts to visit a page they are not authorized to visit, they'll be
automatically sent to the login page. Once they've provided their credentials, calling
RedirectFromLoginPage() will log them
in by calling
SetAuthCookie(); it then redirects the user to the page they were attempting to visit before being
automatically rerouted to the login page.)
This workflow is one both end users and most page developers are familiar with. What it fails to answer, however, is how, exactly, is a user "logged in?" As a user bumps from page to page in the site, how does the site remember that the user is, indeed, "logged in?".
Remembering That a User is Logged In
There are a variety of techniques for maintaining state on a website - querystring parameters, POST parameters, cookies, session variables, and the web server's cache are the most common techniques. With forms-based authentication we clearly need some state management in that the user's "logged on" status must be remembered across multiple page visits. We don't want to force the user to have to re-login every time she visits a new page on our site.
So how can we remember this data? One approach, and an approach used commonly in the days of classic ASP, would be to use
a session variable that would indicate whether or not a user had successfully logged in. (See the article
Simple Authentication for a look at using session variables
in a classic ASP setting for more information.) While this approach definitely works, one downside is that this login data
cannot be persisted across the length of a session. That is, with session variables the state is lost once a user's session
expires, which happens when they close their browser or are inactive for a duration greater than the site's session timeout.
But many sites that utilize forms-based authentication allow the end user to check a 'Remember Me' checkbox that allows the
user to login just once at their home computer and not need to re-enter their login credentials, even days later after
having shut down their computer. In fact, you can configure forms-based authentication to work in this manner. Both the
RedirectFromLoginPage() have as their second parameter a Boolean that indicates
whether or not the user's "logged in" state should persist even after the user's session has ended.
Clearly session variables are not at play with forms-based authentication; instead, cookies are used, which might have been
obvious enough given the fact that the method to login a user (
SetAuthCookie()) has the word 'Cookie' right in it!
Cookies are small text files saved on the client's computer that are sent to the corresponding website with each request.
Ok, so at this point we know we will be using cookies to store authentication information, but this still leaves us with
- What state do we need to maintain in this cookie, and
- How can this state be saved so that it can't be read by nefarious users and can't be spoofed (that is, we don't want to allow someone to fake us out and be able to simulate the state that indicates that they're logged in).
When determining what information we need to persist across page visits, we're really asking two questions: the first is, what information must we have to ensure that a user is, indeed, authenticated; the second is, what information do we need to correctly identify that, yes, this is user X? A naive approach to answering these two questions is, "Well, I'll simply store the user's credentials in the cookie." On each page request we could take the user's credentials passed down in the cookie and determine if they are valid, just like we did on the login page. Additionally, with their credentials handy we certainly can identify the user - we have their username and password, after all!
When storing authentication information in a cookie, you have to be careful because this cookie data is passed over the wire in plain-text (unless your site uses SSL). Additionally, the cookie's contents reside as an unencrypted text file on the end user's computer. If you stored a user's credentials in a cookie, anyone who could access this computer or was sniffing the network traffic could easily peer into the cookie and learn the user's username and password.
Ok, so if we don't want to store the user's credentials in a cookie, then what will we store? At minimum we need to store the logged in user's username, so we can identify who this authenticated user is. But if we just store this authenticated user's username in plain-text, the site can be easily compromised by a hacker. The problem with this plain-text approach is that a nefarious user can "bake" his own cookies - after all, the cookie's contents is a file on the client computer. So what's to stop a hacker from creating his own cookie that has any ol' person's username in it? Once our hacker has done this, they can log in as anyone they like!
Clearly we need a better approach, one that can include the user's username but cannot be compromised or spoofed by hackers.
An Authentication Ticket - Encrypted and Verified for Your Convenience
ASP.NET's forms-based authentication answers the two questions posed earlier by using an authentication ticket. An authentication ticket is like a paper stub that provides your authentication information. This ticket is persisted across page requests by storing it in a cookie. Answering the first question - what state is maintained to identify the authenticated user - the authentication ticket, which is modeled by the
FormsAuthenticationTicketclass, has properties that spell out when the ticket expires, if the ticket is persistent, the authenticated user's username, the date/time the authentication was issued, and a place for customized data that you, as the page developer, can optionally insert.
Answering the second question - how is this information protected from inspection or spoofing - ASP.NET, by default, both encrypts and validates the authentication ticket. That is, the contents of the authentication ticket cookie are encrypted when the cookie is written to the client and, each time its sent to the server, is decrypted back and inspected. The authentication ticket is also validated by default. Validation is accomplished by a MAC, or Machine Authentication Check. This is accomplished by computing a digest hash of the concatenation of the contents of the authentication ticket and tacking on a secret key that is known only to the web server. This hashed value is then appended to the authentication ticket. Upon receiving the authentication ticket from the client the hash can be recomputed and compared against the hash sent by the client. If these two hashes match up, the authentication ticket data has not been modified by any client or in transit; if they do not, something's gone awry and the authentication ticket is considered invalid. This MAC protects both against a nefarious user modifying the cookie or that user "baking" their own cookie.
|A Bit About Hashes|
A hash function is a one-way function that takes some input and quickly computes a digest of the input. Given the
digest and the hash function it is impossible to determine the initial value plugged into the hash function. For example,
consider the hash function h where h(x) = right-most digit of x. With this example, h(10) = 0, h(45) = 5,
and h(14245) = 5. Even knowing h and given the output of h(x) you can't determine the value of
Hashes are commonly used as digital signatures, providing a mechanism to ensure that the contents of some message haven't been modified by some rogue agent or in transit. Hashes can also be used to add an extra level of security when storing passwords. Rather than storing a password in plain-text, the hash of the password can be stored, as discussed in this article: Using MD5 to Encrypt Passwords in a Database.
You can customize the level of protection for the authentication ticket. The default is to apply both encryption
and validation, but you can use neither of these techniques (dangerous!), just encryption, or just validation. Additionally,
you can customize what encryption algorithm is used. Additionally, if you are hosting in a web farm scenario you'll need
to explicitly specify the secret key used by the web server when encrypting the data and when applying the MAC, because
you'll want all servers in the farm to use the same keys. All of these settings can be configured through the
In this article we looked at ASP.NET's forms-based authentication in greater detail. Specifically, we saw how forms-authentication "remembers" that a user is logged in across visits to the website's pages - namely, it uses an authentication ticket stored in a cookie. Working with cookies in this manner can introduce a number of security concerns if not done correctly. To address these issues, the ASP.NET authentication ticket is, by default, both encrypted and digitally signed to ensure that the contents haven't been tampered with. Hopefully this article has provided a bit more information on the internals of forms-based authentication!