In
old-fashioned ASP programming, developers had to create their own
security systems. A common approach was to insert a little snippet of
code at the beginning of every secure page. This code would check for
the existence of a custom cookie. If the cookie didn't exist, the user
would be redirected to a login page, where the cookie would be created
after a successful login.
ASP.NET uses the same
approach in its forms authentication model. You are still responsible
for creating the login page.
However, you don't need to create the security cookie manually, or
check for it in secure pages, because ASP.NET handles these tasks
automatically. You also benefit from ASP.NET's support for
sophisticated validation algorithms, which make it all but impossible
for users to spoof their own cookies or try other hacking tricks to
fool your application into giving them access.
Figure 1 shows a simplified security diagram of the forms authentication model in ASP.NET.
To implement forms-based security, you need to follow three steps:
Set
the authentication mode to forms authentication in the web.config file.
(If you prefer a graphical tool, you can use the WAT during development
or IIS Manager after deployment.)
Restrict anonymous users from a specific page or directory in your application.
You'll walk through these steps in the following sections.
1. Web.config Settings
You define the type of security in the web.config file by using the <authentication> tag.
The following example
configures the application to use forms authentication by using the
<authentication> tag. It also sets several of the most important
settings using a nested <forms> tag. Namely, it sets the name of
the security cookie, the length of time it will be considered valid (in
minutes), and the page that allows the user to log in.
<configuration>
<system.web>
<authentication mode="Forms">
<forms name="MyAppCookie"
loginUrl="~/Login.aspx"
protection="All"
timeout="30" path="/" />
</authentication>
...
</system.web>
</configuration>
Table 1
describes these settings. They all supply default values, so you don't
need to set them explicitly. For a complete list of supported
attributes, consult the Visual Studio Help.
Table 1. Forms Authentication Settings
Attribute | Description |
---|
name | The
name of the HTTP cookie to use for authentication (defaults to
.ASPXAUTH). If multiple applications are running on the same web
server, you should give each application's security cookie a unique
name. |
loginUrl | Your
custom login page, where the user is redirected if no valid
authentication cookie is found. The default value is Login.aspx. |
protection | The
type of encryption and validation used for the security cookie (can be
All, None, Encryption, or Validation). Validation ensures the cookie
isn't changed during transit, and encryption (typically Triple-DES) is
used to encode its contents. The default value is All. |
timeout | The
number of idle minutes before the cookie expires. ASP.NET will refresh
the cookie every time it receives a request. The default value is 30. |
path | The
path for cookies issued by the application. The default value (/) is
recommended, because case mismatches can prevent the cookie from being
sent with a request. |
2. Authorization Rules
If you make these
changes to an application's web.config file and request a page, you'll
notice that nothing unusual happens, and the web page is served in the
normal way. This is because even though you have enabled forms
authentication for your application, you have not restricted anonymous
users. In other words, you've chosen the system you want to use for
authentication, but at the moment none of your pages needs
authentication.
To control who can and can't
access your website, you need to add access control rules to the
<authorization> section of your web.config file. Here's an
example that duplicates the default behavior:
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" />
</authentication>
<authorization>
<allow users="*" />
</authorization>
...
</system.web>
</configuration>
The asterisk (*) is
a wildcard character that explicitly permits all users to use the
application, even those who haven't been authenticated. But even if you
don't include this line in your application's web.config file, this is
still the behavior you'll see, because ASP.NET's default settings allow
all users. (Technically, this behavior happens because there's an
<allow users="*"> rule in the root web.config file. If you're
curious, you can find this file a directory like
c:\Windows\Microsoft.NET\Framework\[Version]\Config, where [Version] is
the version of ASP.NET that's installed, like v4.0.30319.)
To change this behavior, you need to explicitly add a more restrictive rule, as shown here:
<authorization>
<deny users="?" />
</authorization>
The question mark (?) is
a wildcard character that matches all anonymous users. By including
this rule in your web.config file, you specify that anonymous users are
not allowed. Every user must be authenticated, and every user request
will require the security cookie. If you request a page in the
application directory now, ASP.NET will detect that the request isn't
authenticated. It will then redirect the request to the login page
that's specified by the loginUrl attribute in the web.config file. (If
you try this step right now, the redirection process will cause an
error, unless you've already created the login page.)
Now consider what happens if you add more than one rule to the authorization section:
<authorization>
<allow users="*" />
<deny users="?" />
</authorization>
When evaluating rules,
ASP.NET scans through the list from top to bottom and then continues
with the settings in any .config file inherited from a parent
directory, ending with the settings in the base machine.config file. As
soon as it finds an applicable rule, it stops its search. Thus, in the
previous case, it will determine that the rule <allow users="*">
applies to the current request and will not evaluate the second line.
This means these rules will allow all users, including anonymous users.
But consider what happens if these two lines are reversed:
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
Now these rules will deny anonymous users (by matching the first rule) and allow all other users (by matching the second rule).
2.1. Controlling Access to Specific Directories
A common application
design is to place files that require authentication in a separate
directory. With ASP.NET configuration files, this approach is easy.
Just leave the default <authorization> settings in the normal
parent directory, and add a web.config file that specifies stricter
settings in the secured directory. This web.config simply needs to deny
anonymous users (all other settings and configuration sections can be
omitted).
<!-- This web.config file is in a subfolder. -->
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
NOTE
You cannot change the
<authentication> tag settings in the web.config file of a
subdirectory in your application. Instead, all the directories in the
application must use the same authentication system. However, each
directory can have its own authorization rules.
2.2. Controlling Access to Specific Files
Generally, setting file
access permissions by directory is the cleanest and easiest approach.
However, you also have the option of restricting specific files by
adding <location> tags to your web.config file.
The location tags sit
outside the main <system.web> tag and are nested directly in the
base <configuration> tag, as shown here:
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" />
</authentication>
<authorization>
<allow users="*" />
</authorization>
...
</system.web>
<location path="SecuredPage.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
<location path="AnotherSecuredPage.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
</configuration>
In this example, all files
in the application are allowed, except SecuredPage.aspx and
AnotherSecuredPage.aspx, which have an additional access rule denying
anonymous users. Notice that even when you use multiple
<location> sections to supply different sets of authorization
rules, you still only include one <authentication> section.
That's because a web application can use only one type of
authentication.
You can also use the location
tags to set rules for a specific subdirectory. It's up to you whether
you want to use this approach or you prefer to create separate
web.config files for each subdirectory, as described in the previous
section.
|
|
2.3. Controlling Access for Specific Users
The <allow> and
<deny> rules don't need to use the asterisk or question mark
wildcards. Instead, they can specifically identify a user name or a
list of comma-separated user names. For example, the following list
specifically restricts access from three users. These users will not be
able to access the pages in this directory. All other authenticated
users will be allowed.
<authorization>
<deny users="?" />
<deny users="matthew,sarah" />
<deny users="john" />
<allow users="*" />
</authorization>
You'll notice that the
first rule in this example denies all anonymous users. Otherwise, the
following rules won't have any effect, because users won't be forced to
authenticate themselves.
The following rules explicitly allow two users. All other user requests will be denied access, even if they are authenticated.
<authorization>
<deny users="?" />
<allow users="matthew,sarah" />
<deny users="*" />
</authorization>
Don't confuse these user
names with the Windows user account names that are configured on your
web server. When you use forms authentication, your application's
security model is separate from the Windows user account system. Your
application assigns the user name when a user logs in through the login
page. Often, you'll choose user names that correspond to IDs in a
database. The only requirement is that your user names need to be
unique.
3. The WAT
You have another
way to set up your authentication and authorization rules. Rather than
edit the web.config file by hand, you can use the WAT from inside
Visual Studio. The WAT guides you through the process, although you'll
find it's still important to understand what changes are actually being
made to your web.config file. It's also often quicker to enter a list
of authorization rules by hand rather than use the WAT.
To use the WAT for this type of configuration, select Website => ASP.NET Configuration from the menu. Next, click the Security tab. You'll see the window shown in Figure 2,
which gives you links to set the authentication type, define
authorization rules (using the Access Rules section), and enable
role-based security. (Role-based security is an optional higher-level
feature you can use with forms authentication. You'll learn more about
how it works and how to enable it in the next chapter.)
To set an application to use forms authentication, follow these steps:
Click Select Authentication Type.
Choose
the From the Internet option.
Click Done. The appropriate <authorization> tag will be created in the web.config file.
The Select Authentication
options are worded in a slightly misleading way. It's true that
applications that have users connecting from all over the Internet are
sure to use forms authentication. However, applications that run on a
local network might also use forms authentication—it all depends on how
they connect and whether you want to use the information in existing
accounts. In other words, a local intranet gives you the option to use Windows authentication but doesn't require it.
|
|
Next, it's time to define
the authorization rules. To do so, click the Create Access Rules link.
(You can also change existing rules by clicking the Manage Access Rules
link.) Using the slightly convoluted page shown in Figure 3,
you have the ability to create a rule allowing or restricting specific
users to the entire site or a specific page or subfolder. For example,
the rule in Figure 3 will deny the user jenny from the entire site once you click OK to add it.
To manage multiple rules,
you'll need to click the Manage Access Rules link. Now you'll have the
chance to change the order of rules (and hence the priority, as
described earlier), as shown in Figure 4.
If you have a large number of rules to create, you may find it's easier
to edit the web.config file by hand. You might just want to create one
initial rule to make sure it's in the right place and then copy and
paste your way to success.
The Security tab is a little
overwhelming at first glance because it includes a few features you
haven't been introduced to yet. For example, the Security tab also
allows you to create and manage user records and roles, as long as
you're willing to use the prebuilt database structure that ASP.NET
requires. You'll learn more about these details, which are part of a
broad feature called membership, in the next chapter. For now, you'll concentrate on the authentication and authorization process.
4. The Login Page
Once you've specified the
authentication mode and the authorization rules, you need to build the
actual login page, which is an ordinary .aspx page that requests
information from the user and decides whether the user should be
authenticated.
ASP.NET provides a special
FormsAuthentication class in the System.Web.Security namespace, which
provides shared methods that help manage the process. Table 2 describes the most important methods of this class.
Table 2. Members of the FormsAuthentication Class
Member | Description |
---|
FormsCookieName | A read-only property that provides the name of the forms authentication cookie. |
FormsCookiePath | A read-only property that provides the path set for the forms authentication cookie. |
Authenticate() | Checks a user name and password against a list of accounts that can be entered in the web.config file. |
RedirectFromLoginPage() | Logs
the user into an ASP.NET application by creating the cookie, attaching
it to the current response, and redirecting the user to the page
originally requested. |
SignOut() | Logs the user out of the ASP.NET application by removing the current encrypted cookie. |
SetAuthCookie() | Logs
the user into an ASP.NET application by creating and attaching the
forms authentication cookie. Unlike the RedirectFromLoginPage() method,
it doesn't forward the user back to the initially requested page. |
GetRedirectUrl() | Provides
the URL of the originally requested page. You could use this with
SetAuthCookie() to log a user into an application and make a decision
in your code whether to redirect to the requested page or use a more
suitable default page. |
GetAuthCookie() | Creates
the authentication cookie but doesn't attach it to the current
response. You can perform additional cookie customization and then add
it manually to the response. |
HashPasswordForStoringInConfigFile() | Encrypts
a string of text using the specified algorithm (SHA1 or MD5). This
hashed value provides a secure way to store an encrypted password in a
file or database. |
A simple login page can put
these methods to work with little code. To try it, begin by enabling
forms authentication and denying anonymous users in the web.config, as
described earlier:
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" />
</authentication>
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
...
</system.web>
</configuration>
Now, users will be redirected to a login page named Login.aspx that you need to create. Figure 5 shows an example of the simple login page that you might build.
When the user clicks the Login button, the page checks whether the user has typed in the password Secret and then uses the RedirectFromLoginPage() method to log the user in. Here's the complete page code:
Public Partial Class Login
Inherits System.Web.UI.Page
Protected Sub cmdLogin_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdLogin.Click
If txtPassword.Text.ToLower() = "secret" Then
FormsAuthentication.RedirectFromLoginPage(txtName.Text, False)
Else
lblStatus.Text = "Try again."
End If
End Sub
End Class
The
RedirectFromLoginPage() method requires two parameters. The first sets
the name of the user. The second is a Boolean variable that specifies
whether you want to create a persistent cookie (one that stays on the
user's hard drive for a longer period of time). However, in an effort
to be more secure, ASP.NET no longer honors this property in the way it
was originally designed. (You'll learn more about persistent cookies
shortly, in the section named "Persistent Cookies.")
Obviously, the approach used in
the login page isn't terribly secure—it simply checks that the user
supplies a hard-coded password. In a real application, you'd probably
check the user name and password against the information in a database
and sign the user in only if the information matches exactly.
You can test this code with
the FormsSecurity website that's included with the downloadable code
for this chapter. If you request the SecuredPage.aspx file, you'll be
redirected to Login.aspx. After entering the correct password, you'll
return to SecuredPage.aspx.
4.1. Retrieving the User's Identity
Once the user is logged in, you can retrieve the identity through the built-in User property, as shown here:
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
lblMessage.Text = "You have reached the secured page, "
lblMessage.Text &= User.Identity.Name + "."
End Sub
You don't need to place the
code in the login page. Instead, you can use the User object to examine
the current user's identity any time you need to do so.
Figure 6 shows the result of running this code.
You can access the User object
in your code because it's a property of the current Page object. The
User object provides information about the currently logged-in user.
It's fairly simple—in fact, User provides only one property and one
method:
The Identity property lets you retrieve the name of the logged-in user and the type of authentication that was used.
The
IsInRole() method lets you determine whether a user is a member of a
given role (and thus should be given certain privileges). You'll use
IsInRole() later in this chapter.
The User object is standardized
so that it can work with any type of authentication system. One
consequence of this design is that the User.Identity property returns a
different type of object depending on the type of authentication you're
using.
For example, when using
forms authentication, the identity object is an instance of the
FormsIdentity class. When using Windows authentication, you get a
WindowsIdentity object instead. And if the user isn't logged in at all,
you get a GenericIdentity. (All these classes implement the IIdentity
interface, which standardizes them.)
Most of the time, you don't
need to worry about this sleight of hand. But occasionally you might
want to cast the User.Identity property to the more specific type to
get access to an extra piece of information. For example, the
FormsIdentity object provides the security ticket (in a property named
Ticket), which isn't available through the standard IIdentity
interface. This ticket is an instance of the FormsAuthenticationTicket
class, and it provides a few miscellaneous details, like the time the
user logged in and when the ticket will expire. Similarly, the
WindowsIdentity object provides additional information that relates to
Windows accounts (such as whether the current user is using a guest
account or a system account).
|
4.2. Signing Out
Any web application that
uses forms authentication should also feature a prominent logout button
that destroys the forms authentication cookie:
Protected Sub cmdSignOut_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdSignOut.Click
FormsAuthentication.SignOut()
Response.Redirect("~/Login.aspx")
End Sub
4.3. Persistent Cookies
In some situations, you may
be using the forms authentication login for personalization instead of
security. In this situation, you may decide to allow persistent cookies.
A persistent authentication cookie remains on the user's hard drive and
keeps the user signed in for hours, days, or weeks—even if the user
closes and reopens the browser.
Creating a persistent cookie
requires a bit more code than creating a standard forms authentication
cookie. Instead of using the RedirectFromLoginPage() method, you need
to manually create the authentication ticket, set its expiration time,
encrypt it, attach it to the request, and then redirect the user to the
requested page. All of these tasks are easy, but it's important to
perform them all in the correct order. (When using nonpersistent
cookies, the RedirectFromLoginPage() takes care of all these tasks
automatically.)
Persistent cookies also
present a potential security risk, because another user could use the
same computer to access the secured pages, without being forced to log
in. If you want to allow the user to create a persistent cookie, you
should make it optional, because the user may want to access your site
from a public or shared computer. Generally, sites that use this
technique include a check box with text such as Keep Me Logged In.
The following code examines a
check box named chkPersist. If it's selected, the code creates a
persistent cookie that lasts for 20 days (although you can change this
detail to whatever time interval you like).
' Perform the authentication.
If txtPassword.Text.ToLower() = "secret" Then
If chkPersist.Checked Then
' Use a persistent cookie that lasts 20 days.
' The timeout must be specified as a number of minutes.
Dim timeout As Integer
timeout = CInt(TimeSpan.FromDays(20).TotalMinutes)
' Create an authentication ticket.
Dim ticket As New FormsAuthenticationTicket(txtName.Text, _
True, cookietimeout)
' Encrypt the ticket (so people can't steal it as it travels over
' the Internet).
Dim encryptedTicket As String = FormsAuthentication.Encrypt(ticket)
' Create the cookie for the ticket, and put the ticket inside.
Dim cookie As New HttpCookie(FormsAuthentication.FormsCookieName, _
encryptedTicket)
' Give the cookie and the authentication ticket the same expiration.
cookie.Expires = ticket.Expiration
' Attach the cookie to the current response. It will now travel back to
' the client, and then back to the web server with every new request.
HttpContext.Current.Response.Cookies.Set(cookie)
' Send the user to the originally requested page.
Dim requestedPage As String
requestedPage = FormsAuthentication.GetRedirectUrl(txtName.text, False)
Response.Redirect(requestedPage, True)
Else
' Use the standard authentication method.
FormsAuthentication.RedirectFromLoginPage(txtName.Text, False)
End If
End If
It's worth noting that the
FormsAuthentication.SignOut() method will always remove the forms
authentication cookie, regardless of whether it is a normal cookie or a
persistent cookie.