Forms Authentication and Authorization
Forms Authentication and Authorization
Authentication Part 1
Introduction
In Web applications, typically there exist certain parts of the site that only certain individuals, or
groups of individuals, can access. For example, imagine an intranet Web site used to administer the
content on a company's public Internet Web site, where the public Web site lists products sold by the
company. From the administrative site, all company employees might be able to make minor changes
to the products' descriptions, quantity, and other such non-essential information. However, perhaps
only a subset of trusted employees might be able to change the products' prices. And even a smaller
subset of employees would be able to add new products or delete existing products from the database
To handle such a hierarchy of capabilities, a traditional security model to use is to divide users into
roles, and then to assign permissions to various resources on a role-by-role basis. For example, our
fictional company's administrative Web site might be setup so that the President, CEO, and CIO are
made "Administrators" of the product database, and have access to change the product database via
the online interface in any way they desire. The top-level managers might be added to the Price
Changer role, meaning they can change the prices of existing products, while all other company
employees were inserted into the Minor Updates role. Such a security model is typically referred to a
role-based authorization model, as the authorization users enjoy is based upon the role they play
within the system.
In this article we will examine how to utilize role-based authorization in an ASP.NET Web application
using Forms authentication. Specifically, we'll examine the data model needed for implementing roles,
we'll see how to determine what roles a given user belongs to, and then how to restrict (or grant)
access to resources based on a user's roles.
A Quick Security Overview
When thinking or talking about security, there are two fundamental underpinnings which are central to
understanding role-based authorization and security models in general. These two concepts are:
Authentication - authentication is the act of identifying who a user is. In Web applications,
this is typically done by having the user provide some credentials, such as a username and
password.
Authorization - authorization is the act of granting or denying access to a resource based
upon the user attempting to access the resource.
Before we can discuss role-based authorization, we must first think about how we will authenticate our
users. ASP.NET provides three methods for authentication:
1.
2.
3.
Windows Authentication - Useful when working on an intranet, where every user who
needs to be authenticated has a Windows account on the Web server's domain,
Passport Authentication - Uses Microsoft's Passport service, and
Forms Authentication - Prompts the user for a set of credentials (typically username and
password).
This article will focus on using role-based authorization with Forms authentication.
Getting Role-Based Authorization Working - the First Steps
In order to provide role-based authorization, we need some way to model the roles and the users that
participate in these roles. Typically, when using Forms authentication a user is modeled as a row in a
database table. Each user usually contains information pertinent to the Web application, including the
user's credentials (i.e., their username and password).
So, before we can get started at looking at the code necessary for implementing role-based
authorization, we need to provide a data model. Our data model will consist of three related tables:
Users - each record in this table models an individual user. The pertinent fields in this table
include UserID (a unique identifier for each user), Username, and Password.
Groups - each record in this table models a group. The Groups table's pertinent fields are
GroupID and Name. Groups are the classifications to which users can be assigned. For
example, a Web application might have groups like: Administrators, CanEdit, CanDelete,
CanInsert, and so on.
Roles - the Roles table models the many-to-many relationship between Users and Groups.
Specifically, a role maps a user to a group. The Roles table needs only two fields: UserID
and GroupID.
Roles
Users
UserID Username Password
1
Bob
password1
Scott
password2
Jisun
password3
Sam
password4
John
password5
Groups
UserID GroupID
GroupID
Name
Administrators
CanEdit
CanDelete
CanInsert
In this sample database there are five users and four groups. Bob is in the Administrator role, Scott is
in the CanEdit and CanDelete roles, Jisun is in the CanInsert role, Sam is in the CanDelete and
CanInsert roles, and John is not in any roles.
Determining What Roles a User Is In
The .NET Framework contains an HttpApplication class that represents an ASP.NET Web
application. This class has a number of events that can fire during various times of the Web
application's lifecycle. The invent we're interested in is the AuthenticateRequest event, which fires
each time when the ASP.NET security module has established the identity of a user. We can write an
event handler for this event in the Global.asax file. In this event handler we need to determine
what roles this user belongs to.
The following shows the code for the AuthenticateRequest event handler:
The .NET runtime uses the Principal that is attached to the current thread to gain information about
the identity and roles of a user when handling requests that require authorization. To
programmatically assign your own principal settings you simply create an instance of the Principal
class passing in an identity object and a comma delimited string of roles for that identity. The
constructor for the Principal object looks like this:
<authorization>
<allow roles="Administrator,CanEdit" />
<deny users="*" />
</authorization>
This allows those who are in the Administrator or CanEdit roles, while denying all other users.
Membership in roles can also be checked programmatically.
Membership into roles can also be done programmatically using the IsInRole() method of the User
object. For example, you might have an ASP.NET Web page that you wanted to allow everyone to
access, but the amount of details displayed depended upon the user's role. Here is some pseudocode
that implements such functionality:
If User.IsInRole("Administrator") then
' Display sensitive material
ElseIf User.IsInRole("ModerateInfo") then
' Display moderately sensitive material
Else
' Display only bland material
End If
Conclusion
As we saw in this article, implementing role-base authorization in a Forms authenticated environment
requires that we determine the roles a user belongs to in the Application's AuthenticationRequest
event's event handler. Once we have assigned the roles to a user's principal, we can grant or deny
access to various resources either through the <authorization> element in the Web.config file, or
by programmatic means via the IsInRole() method.
If this topic interests you and you plan on implementing it on your Web site, I would first encourage
you to read with a keen eye an excellent publication from Microsoft: Building Secure ASP.NET
Applications: Authentication, Authorization, and Secure Communication. This "best practices" paper
discusses in great detail the IIS and ASP.NET security models, and various techniques for
authentication and authorization. Also worth checking out is How To: Create GenericPrincipal Objects
with Forms Authentication. This article looks at using role-based authorization with Forms
authentication, but instead of hitting the database at each AuthenticationRequest event firing,
this other approach stores the authentication information in an encrypted cookie.
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. 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
SetAuthCookie() and 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 two
questions:
1.
2.
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 FormsAuthenticationTicket class, 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.
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!