Change the web.config
settings of your
application to specify Forms authentication, and then create an
.aspx
login page to collect user credentials and
complete the authentication check.
Modify web.config as follows:
Set the mode
attribute of the
<authentication>
element
to Forms
.
Add a <forms>
child element to the
<authentication>
element to specify key
aspects of the Forms implementation:
<authentication mode="Forms"><forms name=".ASPNETCookbook"
loginUrl="Login.aspx"
protection="All"
timeout="30"
path="/">
</forms>
</authentication>
Add <deny>
and
<allow>
child elements to the
<authorization>
element to deny access to
anonymous users and allow access to all who have been authenticated:
<authorization><deny users="?" /> <!-- Deny anonymous users -->
<allow users="*" /> <!-- Allow all authenticated users -->
</authorization>
In the .aspx
file for the login page:
Add the fields required to collect the data the application needs to authenticate the user. Most applications require, at a minimum, a user login ID and password, but you can specify whatever your application requires.
Add a Login button.
(Optional) Include a checkbox for users to indicate that they want to be remembered between sessions. (You will need to add some code to the code-behind class to persist the authentication cookie on the client machine.)
In the code-behind class for the login page, use the .NET language of your choice to:
Use the Login button click event to verify the user credentials.
If the user credentials are valid, create a Forms authentication
cookie and add it to the cookie collection returned to the browser by
calling the SetAuthCookie
method of the
FormsAuthentication
class.
(Optional) Set the Forms authentication cookie to be persisted on the client machine.
Redirect the user to the appropriate application start page using
Response.Redirect
.
The code we’ve created to illustrate this solution
is shown in Example 8-1 through Example 8-4. Example 8-1 shows the
modifications we make to web.config
to restrict
access to all pages. Example 8-2 shows the
.aspx
file for the login page. Example 8-3 (VB) and Example 8-4 (C#)
show the code-behind class for the login page. Figure 8-1 shows the login page produced by the
application.
ASP.NET runs within the context of IIS and all requests must first pass through IIS, so setting up the security for an ASP.NET application always starts with setting up security in IIS. For this recipe, we do not want IIS to perform any authentication. Therefore, the web site (or virtual directory) must be set up to allow anonymous access. (We won’t take you through setting up anonymous access in IIS—it is relatively easy to do and is well documented in MSDN. Just search for “IIS authentication”.)
The first step to restricting access to all pages of an application
is to enable ASP.NET security. This is done by setting the
mode
attribute of the
<authentication>
element of the
web.config
file to Forms
.
(Other options are Windows
and
Passport
.)
The second step is to add a <forms>
child
element to the <authentication>
element in
order to specify the details of the Forms implementation. The
<forms>
element has the following
attributes:
name
Defines the name of the HTTP cookie used by ASP.NET to maintain the user authentication information. Care should be taken when naming the cookie. If two applications on the same server use the same cookie name, “cross authentication” could occur.
loginUrl
Defines the page to which ASP.NET will redirect users when they attempt to access pages in your application without being logged in. The login page should provide the fields required to authenticate the user, typically a login ID and password or whatever else your application requires.
protection
Defines the protection method used for the cookie.
Possible values are All
, None
,
Encryption
, and Validation
.
Validation
specifies that the cookie data will be
validated to ensure it was not altered in transit.
Encryption
specifies that the cookie is encrypted.
All
specifies that data validation and encryption
will be used. None
specifies no protection will be
provided for the cookie information. The default is
All
and is highly recommended because it offers
the highest level of protection for this authentication cookie.
timeout
Defines the amount of time in minutes before the cookie expires. The value
provided here should be at least as long at the timeout for the
session. Making the value shorter than the session timeout can result
in a user being redirected to the page defined by the
loginUrl
before the session times out.
path
Defines the path of cookies issued by the
application. Be aware that most browsers treat the path as
case-sensitive and will not return the cookie for a request that does
not match the value provided for the path attribute. The result will
be having the users redirected as if they were not logged in. Unless
your application requires specifying the path, we recommend that you
leave the path as "/
“.
Here is an example of the <forms>
element
and its attributes:
<authentication mode="Forms"><forms name=".ASPNETCookbook"
loginUrl="Login.aspx"
protection="All"
timeout="30"
path="/">
</forms>
</authentication>
The next step is to modify web.config
to deny
access to all anonymous users and allow access to all users who have
been authenticated. This is done by
adding
<deny>
and <allow>
child elements to the <authorization>
element:
<authorization><deny users="?" /> <!— Deny anonymous users —>
<allow users="*" /> <!— Allow all authenticated users —>
</authorization>
Your application login page should provide the fields required to
enter the data needed to authenticate the user. This is typically a
login ID and password, which you gather via text boxes, but can be
whatever your application requires. ASP.NET provides the ability to
persist the authentication cookie on the client machine. If your
application supports “auto login”
from a persistent cookie, you should provide a checkbox for the user
to indicate that she wants to be remembered between sessions. In
addition, your login page should include a button to initiate the
login process after the data has been entered. How we have done this
for our application is illustrated in Figure 8-1
and in the .aspx
file (Example 8-2).
In the code-behind for the login page, use the login button click
event to verify the user credentials. In Example 8-3 and Example 8-4, for example,
the database is queried for a user matching the entered login ID and
password using a DataCommand
and a
DataReader
. After the
DataReader
is created, the record pointer is by
default positioned before the first record in the reader. By calling
the Read
method in the If
statement, you can check to see if the user credentials are found and
read the user credentials from the database at the same time. If the
user credentials are found in the database, the
Read
method will return True
.
Otherwise, it will return False
.
If the user credentials are valid, the Forms authentication cookie
needs to be created and added to the cookie collection returned to
the browser. This can be done by calling the
SetAuthCookie
method of
the
FormsAuthentication
class, passing the user name
and a Boolean value indicating whether the value should be persisted
on the client machine. To persist the authentication cookie and allow
the user to access the application on subsequent sessions without
logging in, set the second parameter to True
. If
the second parameter is set to False
, the
authentication cookie is stored in memory on the client and is
destroyed when the session expires or the user closes the browser.
Because the SetAuthCookie
method is static, it is
not necessary to create a FormAuthentication
object to use the method. The SetAuthCookie
method
is the simplest approach to creating and adding the cookie to the
cookie collection, but is not very flexible. For an example of a more
flexible approach that allows you to store additional data in the
authentication cookie, see Recipe 8.3.
After the application has created the authentication cookie, the user
should be redirected to the appropriate start page. When ASP.NET
redirects the user to the login page defined in your
web.config
file, it automatically appends the
name of the originally requested page to the redirected URL, as shown
next. You can use this information to redirect users to the
originally requested page or simply redirect them to a fixed page, as
illustrated in Example 8-3 (VB) and Example 8-4 (C#).
http://localhost/ASPNetBook/Login.aspx?ReturnUrl=Home.aspx
You must use Response.Redirect
to redirect the
user to the next page. Response.Redirect
returns
information to the browser instructing it to redirect to the
indicated page. This round trip to the client browser writes the
authentication cookie to the browser so it will be returned to the
server on subsequent page requests. This cookie is what ASP.NET uses
to determine if the user has been authenticated. Using other
mechanisms, such as Server.Transfer
, will
not cause the
authentication cookie to be written to the browser, resulting in the
user being redirected back to the login page.
ASP.NET provides in a single method call (the
RedirectFromLoginPage
method of the
FormsAuthentication
class) the ability to create
the authentication cookie, add it to the cookie collection, and
redirect the user back the original page. This method sounds like it
would save a few lines of code; however, if the user was not
redirected to the login page (that is, if her original request was
the login page), ASP.NET will redirect her to a page named
Default.aspx
. If your application does not use
Default.aspx
as the
“home” page, using
RedirectFromLoginPage
creates more problems than
it solves.
No other code is required. In other words, by using the simple code just described (without adding code to each page, as is required in classic ASP), access to all of the pages in your application is restricted to logged-in users.
So how does ASP.NET do this so easily? When your application is
configured to use Forms authentication, ASP.NET looks for the cookie
defined by the name
attribute of the
<forms>
element in
web.config
for every page requested from your
application. If the cookie does not exist, ASP.NET assumes the user
is not logged in and redirects the user to the page defined by the
loginUrl
attribute. If the cookie does exist,
ASP.NET assumes the user is authenticated and passes the request on
to the requesting page. In addition, when the cookie exists, ASP.NET
creates a user principal object with the information found in the
authentication cookie. The user principal object
(or principal object for short)
represents the security context under
which code is running. This information is available to your
application by accessing the User
object in the
current context. To get the user name you added to the authentication
cookie, use the line of code shown here:
userName = Context.User.Identity.Name userName = Context.User.Identity.Name;
Applications that provide the ability to log in—particularly those that provide the ability to persist the information on the client machine to eliminate the need to log in on subsequent visits—should provide the ability to log out. In this context, logging out simply destroys the authentication cookie on the client machine, which then requires the user to log in again to gain access to your application. This can be accomplished with the line of code shown here:
FormsAuthentication.SignOut( ) FormsAuthentication.SignOut( );
ASP.NET can provide security only for files that are mapped to the
ASP.NET ISAPI DLL. By default, these are files with the extensions
.asax
, .ascx
,
.ashx
, .asmx
,
.aspx
, .axd
,
.vsdisco
, .rem
,
.soap
, .config
,
.cs
, .csproj
,
.vb
, .vbproj
,
.webinfo
, .licx
, and
.resx
and resources. Any other file
types—such as .gif
,
.jpg
, .txt
,
.js
, and the like—are not protected by
ASP.NET security. If access to these file types must also be
restricted, they will have to be added to the list of file types
processed by the ASP.NET ISAPI DLL. This can be done in the
application configuration section of the IIS properties of your
application. Requiring these file types to be processed by ASP.NET
will affect performance of your application due to the extra
processing required for the images, text files, JavaScript, and the
like.
Recipe 8.3; MSDN documentation for IIS setup (search for “IIS authentication”)
Example 8-1. Changes to web.config to restrict access to all pages
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> ..<authentication mode="Forms">
<forms name=".ASPNETCookbookVB"
loginUrl="Login.aspx"
protection="All"
timeout="30"
path="/">
</forms>
</authentication>
<authorization>
<deny users="?" /> <!-- Deny anonymous user -->
<allow users="*" /> <!-- Allow all authenticated users -->
</authorization>
.. </system.web> </configuration>
Example 8-2. Login page (.aspx)
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="Login.aspx.vb" Inherits="ASPNetCookbook.VBSecurity81.Login"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Login</title> <link rel="stylesheet" href="css/ASPNetCookbook.css"> </head> <body leftmargin="0" marginheight="0" marginwidth="0" topmargin="0"> <form id="frmSecurity" method="post" runat="server"> <table width="100%" cellpadding="0" cellspacing="0" border="0"> <tr> <td align="center"> <img src="images/ASPNETCookbookHeading_blue.gif"> </td> </tr> <tr> <td class="dividerLine"> <img src="images/spacer.gif" height="6" border="0"></td> </tr> </table> <table width="90%" align="center" border="0"> <tr> <td><img src="images/spacer.gif" height="10" border="0"></td> </tr> <tr> <td align="center" class="PageHeading"> Block Access To All Pages (VB) </td> </tr> <tr> <td><img src="images/spacer.gif" height="10" border="0"></td> </tr> <tr> <td align="center"> <table> <tr> <td class="LabelText">Login ID: </td> <td><asp:TextBox ID="txtLoginID" Runat="server"
CssClass="LabelText" />
</td> </tr> <tr> <td class="LabelText">Password: </td> <td><asp:TextBox ID="txtPassword" Runat="server"
CssClass="LabelText" TextMode="Password" />
</td> </tr> <tr> <td colspan="2" align="center"><asp:CheckBox ID="chkRememberMe" Runat="server"
CssClass="LabelText" Text="Remember Me" />
</td> </tr> <tr> <td colspan="2" align="center"> <br /><input id="btnLogin" runat="server"
type="button" value="Login" />
</td> </tr> <tr> <td colspan="2" align="center"> <br /> <input type="button" value="Attempt Access without Login" onclick="document.location='Home.aspx'" /> </td> </tr> </table> </td> </tr> </table> </form> </body> </html>
Example 8-3. Login page code-behind (.vb)
Option Explicit On Option Strict On '----------------------------------------------------------------------------- ' ' Module Name: Login.aspx.vb ' ' Description: This module provides the code behind for the ' Login.aspx page ' '***************************************************************************** Imports Microsoft.VisualBasic Imports System.Configuration Imports System.Data Imports System.Data.OleDb Imports System.Web.Security Imports System.Web.UI.HtmlControls Imports System.Web.UI.WebControls Namespace ASPNetCookbook.VBSecurity81 Public Class Login Inherits System.Web.UI.Page 'controls on the form Protected txtLoginID As TextBox Protected txtPassword As TextBox Protected chkRememberMe As CheckBox Protected WithEvents btnLogin As HtmlInputButton '************************************************************************* ' ' ROUTINE: Page_Load ' ' DESCRIPTION: This routine provides the event handler for the page load ' event. It is responsible for initializing the controls ' on the page. '------------------------------------------------------------------------- Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load 'Put user code to initialize the page here End Sub 'Page_Load '************************************************************************* ' ' ROUTINE: btnLogin_ServerClick ' ' DESCRIPTION: This routine provides the event handler for the login ' button click event. It is responsible for authenticating ' the user and redirecting to the next page if the user ' is authenticated. '------------------------------------------------------------------------- Private Sub btnLogin_ServerClick(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles btnLogin.ServerClick 'name of querystring parameter containing return URLConst QS_RETURN_URL As String = "ReturnURL"
Dim dbConn As OleDbConnection Dim dCmd As OleDbCommand Dim dr As OleDbDataReader Dim strConnection As String Dim strSQL As String Dim nextPage As String Try'get the connection string from web.config and open a connection
'to the database
strConnection = _
ConfigurationSettings.AppSettings("dbConnectionString")
dbConn = New OleDb.OleDbConnection(strConnection)
dbConn.Open( )
'check to see if the user exists in the database
strSQL = "SELECT (FirstName + ' ' + LastName) AS UserName " & _
"FROM AppUser " & _
"WHERE LoginID=? AND " & _
"Password=?"
dCmd = New OleDbCommand(strSQL, dbConn)
dCmd.Parameters.Add(New OleDbParameter("LoginID", _
txtLoginID.Text))
dCmd.Parameters.Add(New OleDbParameter("Password", _
txtPassword.Text))
dr = dCmd.ExecuteReader( )
If (dr.Read( )) Then
'user credentials were found in the database so notify the system
'that the user is authenticated
FormsAuthentication.SetAuthCookie(CStr(dr.Item("UserName")), _
chkRememberMe.Checked)
'get the next page for the user
If (Not IsNothing(Request.QueryString(QS_RETURN_URL))) Then
'user attempted to access a page without logging in so redirect
'them to their originally requested page
nextPage = Request.QueryString(QS_RETURN_URL)
Else
'user came straight to the login page so just send them to the
'home page
nextPage = "Home.aspx"
End If
'Redirect user to the next page
'NOTE: This must be a Response.Redirect to write the cookie to the
' user's browser. Do NOT change to Server.Transfer which
' does not cause around trip to the client browser and thus
' will not write the authentication cookie to the client
' browser.
Response.Redirect(nextPage, True)
Else
'user credentials do not exist in the database - in a production
'application this should output an error message telling the user
'that the login ID or password was incorrect
End If
Finally 'cleanup If (Not IsNothing(dr)) Then dr.Close( ) End If If (Not IsNothing(dbConn)) Then dbConn.Close( ) End If End Try End Sub 'btnLogin_ServerClick End Class 'Login End Namespace
Example 8-4. Login page code-behind (.cs)
//---------------------------------------------------------------------------- // // Module Name: Login.aspx.cs // // Description: This module provides the code behind for the // Login.aspx page // //**************************************************************************** using System; using System.Configuration; using System.Data; using System.Data.OleDb; using System.Web.Security; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace ASPNetCookbook.CSSecurity81 { public class Login : System.Web.UI.Page { // controls on the form protected System.Web.UI.WebControls.TextBox txtLoginID; protected System.Web.UI.WebControls.TextBox txtPassword; protected System.Web.UI.WebControls.CheckBox chkRememberMe; protected System.Web.UI.HtmlControls.HtmlInputButton btnLogin; //************************************************************************ // // ROUTINE: Page_Load // // DESCRIPTION: This routine provides the event handler for the page // load event. It is responsible for initializing the // controls on the page. //------------------------------------------------------------------------ private void Page_Load(object sender, System.EventArgs e) { // wire the login button this.btnLogin.ServerClick += new EventHandler(this.btnLogin_ServerClick); } // Page_Load //************************************************************************ // // ROUTINE: btnLogin_ServerClick // // DESCRIPTION: This routine provides the event handler for the login // button click event. It is responsible for // authenticating the user and redirecting to the next // page if the user is authenticated. // //------------------------------------------------------------------------ private void btnLogin_ServerClick(Object sender, System.EventArgs e) { // name of querystring parameter containing return URLconst String QS_RETURN_URL = "ReturnURL";
OleDbConnection dbConn = null; OleDbCommand dCmd = null; OleDbDataReader dr = null; String strConnection = null; String strSQL = null; String nextPage = null; try {// get the connection string from web.config and open a connection
// to the database
strConnection =
ConfigurationSettings.AppSettings["dbConnectionString"];
dbConn = new OleDbConnection(strConnection);
dbConn.Open( );
// check to see if the user exists in the database
strSQL = "SELECT (FirstName + ' ' + LastName) AS UserName " +
"FROM AppUser " +
"WHERE LoginID=? AND " +
"Password=?";
dCmd = new OleDbCommand(strSQL, dbConn);
dCmd.Parameters.Add(new OleDbParameter("LoginID",
txtLoginID.Text));
dCmd.Parameters.Add(new OleDbParameter("Password",
txtPassword.Text));
dr = dCmd.ExecuteReader( );
if (dr.Read( ))
{
// user credentials were found in the database so notify the system
// that the user is authenticated
FormsAuthentication.SetAuthCookie((String)(dr["UserName"]),
chkRememberMe.Checked);
// get the next page for the user
if (Request.QueryString[QS_RETURN_URL] != null)
{
// user attempted to access a page without logging in so redirect
// them to their originally requested page
nextPage = Request.QueryString[QS_RETURN_URL];
}
else
{
// user came straight to the login page so just send them to the
// home page
nextPage = "Home.aspx";
}
// Redirect user to the next page
// NOTE: This must be a Response.Redirect to write the cookie to
// the user's browser. Do NOT change to Server.Transfer
// which does not cause around trip to the client browser
// and thus will not write the authentication cookie to the
// client browser.
Response.Redirect(nextPage, true);
}
else
{
// user credentials do not exist in the database - in a production
//application this should output an error message telling the user
// that the login ID or password was incorrect.
}
} // try finally { // cleanup if (dr != null) { dr.Close( ); } if (dbConn != null) { dbConn.Close( ); } } // finally } // btnLogin_ServerClick } // Login }