Aspnet Tutorial07 UserAuth VB
Aspnet Tutorial07 UserAuth VB
Introduction
Most web applications that offer user accounts do so in part to restrict certain visitors from
accessing certain pages within the site. In most online messageboard sites, for example, all
users – anonymous and authenticated – are able to view the messageboard’s posts, but
only authenticated users can visit the web page to create a new post. And there may be
administrative pages that are only accessible to a particular user (or a particular set of
users). Moreover, page-level functionality can differ on a user-by-user basis. When viewing
a list of posts, authenticated users are shown an interface for rating each post, whereas this
interface is not available to anonymous visitors.
ASP.NET makes it easy to define user-based authorization rules. With just a bit of markup in
Web.config, specific web pages or entire directories can be locked down so that they are
only accessible to a specified subset of users. Page-level functionality can be turned on or
off based on the currently logged in user through programmatic and declarative means.
In this tutorial we will look at limiting access to pages and restricting page-level
functionality through a variety of techniques. Let’s get started!
Figure 1 depicts the interaction that occurs when an anonymous visitor attempts to access a
resource that is not available to anonymous users. In such a case, the anonymous visitor is
redirected to the login page with the page she attempted to visit specified in the
querystring. Once the user has successfully logged on, she will be automatically redirected
back to the resource she was initially attempting to view.
Imagine that our website had its URL authorization rules configured such that the ASP.NET
page OnlyTito.aspx was accessibly only to Tito. Now, imagine that Sam visits the site, logs
on, and then attempts to visit OnlyTito.aspx. The UrlAuthorizationModule will halt the
request lifecycle and return an HTTP 401 Unauthorized status, which the
FormsAuthenticationModule will detect and then redirect Sam to the login page. Since
Sam has already logged in, though, she may wonder why she has been sent back to the
login page. She might reason that her login credentials were lost somehow, or that she
entered invalid credentials. If Sam reenters her credentials from the login page she will be
logged on (again) and redirected to OnlyTito.aspx. The UrlAuthorizationModule will
detect that Sam cannot visit this page and she will be returned to the login page.
The workflow illustrated in Figure 2 can quickly befuddle even the most computer savvy
visitor. We will look at ways to prevent this confusing cycle in Step 2.
Note: ASP.NET uses two mechanisms to determine whether the current user can
access a particular web page: URL authorization and file authorization. File
authorization is implemented by the FileAuthorizationModule, which determines
authority by consulting the requested file(s) ACLs. File authorization is most
commonly used with Windows authentication because ACLs are permissions that
apply to Windows accounts. When using forms authentication, all operating system-
and file system-level requests are executed by the same Windows account,
regardless of the user visiting the site. Since this tutorial series focuses on forms
authentication, we will not be discussing file authorization.
IIS 7, however, allows for integrated IIS and ASP.NET pipelines. With a few configuration
settings you can setup IIS 7 to invoke the UrlAuthorizationModule for all requests,
meaning that URL authorization rules can be defined for files of any type. Additionally, IIS 7
includes its own URL authorization engine. For more information on ASP.NET integration and
IIS 7’s native URL authorization functionality, see Understanding IIS7 URL Authorization.
For a more in-depth look at ASP.NET and IIS 7 integration, pick up a copy of Shahram
Khosravi’s book, Professional IIS 7 and ASP.NET Integrated Programming (ISBN: 978-
0470152539).
In a nutshell, in versions prior to IIS 7, URL authorization rules are only applied to resources
handled by the ASP.NET runtime. But with IIS 7 it is possible to use IIS’s native URL
authorization feature or to integrate ASP.NET’s UrlAuthorizationModule into IIS’s HTTP
pipeline, thereby extending this functionality to all requests.
Note: There are some subtle yet important differences in how ASP.NET’s
UrlAuthorizationModule and IIS 7’s URL authorization feature process the
authorization rules. This tutorial does not examine IIS 7’s URL authorization
functionality or the differences in how it parses authorization rules compared to the
UrlAuthorizationModule. For more information on these topics, refer to the IIS 7
documentation on MSDN or at www.iis.net.
Step 1: Defining URL Authorization Rules in
Web.config
The UrlAuthorizationModule determines whether to grant or deny access to a requested
resource for a particular identity based on the URL authorization rules defined in the
application’s configuration. The authorization rules are spelled out in the <authorization>
element in the form of <allow> and <deny> child elements. Each <allow> and <deny> child
element can specify:
A particular user
A comma-delimited list of users
All anonymous users, denoted by a question mark (?)
All users, denoted by an asterisk (*)
The following markup illustrates how to use the URL authorization rules to allow users Tito
and Scott and deny all others:
<authorization>
<allow users="Tito, Scott" />
<deny users="*" />
</authorization>
The <allow> element defines what users are permitted – Tito and Scott – while the <deny>
element instructs that all users are denied.
Note: The <allow> and <deny> elements can also specify authorization rules for
roles. We will examine role-based authorization in a future tutorial.
The following setting grants access to anyone other than Sam (including anonymous
visitors):
<authorization>
<deny users="Sam" />
</authorization>
To allow only authenticated users, use the following configuration, which denies access to all
anonymous users:
<authorization>
<deny users="?" />
</authorization>
The authorization rules are defined within the <system.web> element in Web.config and
apply to all of the ASP.NET resources in the web application. Oftentimes, an application has
different authorization rules for different sections. For example, at an eCommerce site, all
visitors may peruse the products, see product reviews, search the catalog, and so on.
However, only authenticated users may reach the checkout or the pages to manage one’s
shipping history. Moreover, there may be portions of the site that are only accessible by
select users, such as site administrators.
ASP.NET makes it easy to define different authorization rules for different files and folders in
the site. The authorization rules specified in the root folder’s Web.config file apply to all
ASP.NET resources in the site. However, these default authorization settings can be
overridden for a particular folder by adding a Web.config with an <authorization> section.
Let’s update our website so that only authenticated users can visit the ASP.NET pages in the
Membership folder. To accomplish this we need to add a Web.config file to the Membership
folder and set its authorization settings to deny anonymous users. Right-click the
Membership folder in the Solution Explorer, choose the Add New Item menu from the
context menu, and add a new Web Configuration File named Web.config.
Update the configuration file in the Membership folder so that it prohibits access to
anonymous users.
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
To test out this change, visit the homepage in a browser and make sure you are logged out.
Since the default behavior of an ASP.NET application is to allow all visitors, and since we
didn’t make any authorization modifications to the root directory’s Web.config file, we are
able to visit the files in the root directory as an anonymous visitor.
Click on the “Creating User Accounts” link found in the left column. This will take you to the
~/Membership/CreatingUserAccounts.aspx. Since the Web.config file in the Membership
folder defines authorization rules to prohibit anonymous access, the
UrlAuthorizationModule aborts the request and returns an HTTP 401 Unauthorized status.
The FormsAuthenticationModule modifies this to a 302 Redirect status, sending us to the
login page. Note that the page we were attempting to access
(CreatingUserAccounts.aspx) is passed to the login page via the ReturnUrl querystring
parameter.
Figure 5: Since the URL Authorization Rules Prohibit Anonymous Access, We are
Redirected to the Login Page
To illustrate using the <location> element to override the configuration settings for a
specific resource, let’s customize the authorization settings so that only Tito can visit
CreatingUserAccounts.aspx. To accomplish this, add a <location> element to the
Membership folder’s Web.config file and update its markup so that it looks like the
following:
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
<location path="CreatingUserAccounts.aspx">
<system.web>
<authorization>
<allow users="Tito" />
<deny users="*" />
</authorization>
</system.web>
</location>
</configuration>
The <authorization> element in <system.web> defines the default URL authorization rules
for ASP.NET resources in the Membership folder and its subfolders. The <location> element
allows us to override these rules for a particular resource. In the above markup the
<location> element references the CreatingUserAccounts.aspx page and specifies its
authorization rules such as to allow Tito, but deny everyone else.
To test out this authorization change, start by visiting the website as an anonymous user. If
you attempt to visit any page in the Membership folder, such as
UserBasedAuthorization.aspx, the UrlAuthorizationModule will deny the request and
you will be redirected to the login page. After logging in as, say, Scott, you can visit any
page in the Membership folder except for CreatingUserAccounts.aspx. Attempting to visit
CreatingUserAccounts.aspx logged on as anyone but Tito will result in an unauthorized
access attempt, redirecting you back to the login page.
Since the UrlAuthorizationModule processes the authorization rules from the top down,
stopping at any match, it is important to have the more specific rules come before the less
specific ones. That is, to define authorization rules that forbid Jisun and anonymous users,
but allow all other authenticated users, you would start with the most specific rule – the one
impacting Jisun – and then proceed to the less specific rules – those allowing all other
authenticated users, but denying all anonymous users. The following URL authorization rules
implements this policy by first denying Jisun, and then denying any anonymous user. Any
authenticated user other than Jisun will be granted access because neither of these <deny>
statements will match.
<authorization>
<deny users="Jisun" />
<deny users="?" />
</authorization>
Returning an authenticated user to the login page is likely to confuse them since they have
already logged into the system. With a little bit of work we can improve this workflow by
redirecting authenticated users who make unauthorized requests to a page that explains
that they have attempted to access a restricted page.
Start by creating a new ASP.NET page in the web application’s root folder named
UnauthorizedAccess.aspx; don’t forget to associate this page with the Site.master
master page. After creating this page, remove the Content control that references the
LoginContent ContentPlaceHolder so that the master page’s default content will be
displayed. Next, add a message that explains the situation, namely that the user attempted
to access a protected resource. After adding such a message, the
UnauthorizedAccess.aspx page’s declarative markup should look similar to the following:
To accomplish this, add the following code to the login page’s Page_Load event handler:
This customized workflow presents a more sensible and straightforward user experience by
short circuiting the cycle depicted in Figure 2.
Consider the case of an eCommerce website that allows authenticated visitors to review
their products. When an anonymous user visits a product’s page, they would see just the
product information and would not be given the opportunity to leave a review. However, an
authenticated user visiting the same page would see the reviewing interface. If the
authenticated user had not yet reviewed this product, the interface would enable them to
submit a review; otherwise it would show them their previously-submitted review. To take
this scenario a step further, the product page might show additional information and offer
extended features for those users that work for the eCommerce company. For example, the
product page might list the inventory in stock and include options to edit the product’s price
and description when visited by an employee.
Let’s create a page that lists the files in a particular directory within a GridView. Along with
listing each file’s name, size, and other information, the GridView will include two columns
of LinkButtons: one titled “View” and one titled “Delete”. If the “View” LinkButton is clicked,
the contents of the selected file will be displayed; if the “Delete” LinkButton is clicked, the
file will be deleted. Let’s initially create this page such that its view and delete functionality
is available to all users. In the “Using the LoginView Control” and “Programmatically
Limiting Functionality” sections we will see how to enable or disable these features based on
the user visiting the page.
Note: The ASP.NET page we are about to build uses a GridView control to display a
list of files. Since this tutorial series focuses on forms authentication, authorization,
user accounts, and roles, I do not want to spend too much time discussing the inner
workings of the GridView control. While this tutorial provides specific step-by-step
instructions for setting up this page, it does not delve into the details of why certain
choices were made, or what effect particular properties have on the rendered output.
For a thorough examination of the GridView control, consult my Working with Data in
ASP.NET 2.0 tutorial series.
After configuring the GridView’s columns, click OK to close the Fields dialog box. From the
Properties window, set the GridView’s DataKeyNames property to FullName. At this point the
GridView’s declarative markup should look like the following:
With the GridView’s markup created, we’re ready to write the code that will retrieve the files
in a particular directory and bind them to the GridView. Add the following code to the page’s
Page_Load event handler:
FilesGrid.DataSource = files
FilesGrid.DataBind()
End If
End Sub
The above code uses the DirectoryInfo class to obtain a list of the files in the application’s
root folder. The GetFiles() method returns all of the files in the directory as an array of
FileInfo objects, which is then bound to the GridView. The FileInfo object has an
assortment of properties, such as Name, Length, and IsReadOnly, among others. As you can
see from its declarative markup, the GridView displays just the Name and Length properties.
Note: The DirectoryInfo and FileInfo classes are found in the System.IO
namespace. Therefore, you will either need to preface these class names with their
namespace names or have the namespace imported into the class file (via Imports
System.IO).
Take a moment to visit this page through a browser. It will display the list of files residing in
the application’s root directory. Clicking any of the “View” or “Delete” LinkButtons will cause
a postback, but no action will occur because we’ve yet to create the necessary event
handlers.
Figure 7: The GridView Lists the Files in the Web Application’s Root Directory
We need a means to display the contents of the selected file. Return to Visual Studio and
add a TextBox named FileContents above the GridView. Set its TextMode property to
MultiLine and its Columns and Rows properties to “95%” and 10, respectively.
Next, create an event handler for the GridView’s SelectedIndexChanged event and add the
following code:
Note: If you view the contents of a file that contains HTML markup, and then
attempt to view or delete a file, you will receive an
HttpRequestValidationException error. This occurs because on postback the
TextBox’s contents are sent back to the web server. By default, ASP.NET raises an
HttpRequestValidationException error whenever potentially dangerous postback
content, such as HTML markup, is detected. To disable this error from occurring, turn
off request validation for the page by adding ValidateRequest="false" to the
@Page directive. For more information on the benefits of request validation as well as
what precautions you should take when disabling it, read Request Validation –
Preventing Script Attacks.
Finally, add an event handler with the following code for the GridView’s RowDeleting event:
Protected Sub FilesGrid_RowDeleting(ByVal sender As Object, ByVal e As
System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles
FilesGrid.RowDeleting
Dim fullFileName As String =
FilesGrid.DataKeys(e.RowIndex).Value.ToString()
FileContents.Text = String.Format("You have opted to delete {0}.",
fullFileName)
The code simply displays the full name of the file to delete in the FileContents TextBox
without actually deleting the file.
Figure 9: Clicking the Delete Button Does Not Actually Delete the File
In Step 1 we configured the URL authorization rules to prohibit anonymous users from
viewing the pages in the Membership folder. In order to better exhibit fine grain
authentication, let’s allow anonymous users to visit the UserBasedAuthorization.aspx
page, but with limited functionality. To open this page up to be accessed by all users, add
the following <location> element to the Web.config file in the Membership folder:
<location path="UserBasedAuthorization.aspx">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
After adding this <location> element, test the new URL authorization rules by logging out
of the site. As an anonymous user you should be permitted to visit the
UserBasedAuthorization.aspx page.
The Web controls in the LoginView’s templates are no longer directly accessible from the
code-behind class. For example, the FilesGrid GridView’s SelectedIndexChanged and
RowDeleting event handlers currently reference the FileContents TextBox control with
code like:
FileContents.Text = text
However, this code is no longer valid. By moving the FileContents TextBox into the
LoggedInTemplate the TextBox cannot be directly accessed. Instead, we must use the
FindControl("controlId") method to programmatically reference the control. Update the
FilesGrid event handlers to reference the TextBox like so:
After moving the TextBox to the LoginView’s LoggedInTemplate and updating the page’s
code to reference the TextBox using the FindControl("controlId") pattern, visit the page
as an anonymous user. As Figure 10 shows, the FileContents TextBox is not displayed.
However, the “View” LinkButton is still displayed.
Figure 10: The LoginView Control Only Renders the FileContents TextBox for
Authenticated Users
One way to hide the “View” button for anonymous users is to convert the GridView field into
a TemplateField. This will generate a template that contains the declarative markup for the
“View” LinkButton. We can then add a LoginView control to the TemplateField and place the
LinkButton within the LoginView’s LoggedInTemplate, thereby hiding the “View” button
from anonymous visitors. To accomplish this, click on the “Edit Columns” link from the
GridView’s Smart Tag to launch the Fields dialog box. Next, select the Select button from
the list in the lower left corner and then click the “Convert this field to a TemplateField” link.
Doing so will modify the field’s declarative markup from:
To:
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server"
CausesValidation="False"
CommandName="Select" Text="View"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
At this point, we can add a LoginView to the TemplateField. The following markup displays
the “View” LinkButton only for authenticated users.
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
<asp:LinkButton ID="LinkButton1" runat="server"
CausesValidation="False"
CommandName="Select" Text="View"></asp:LinkButton>
</LoggedInTemplate>
</asp:LoginView>
</ItemTemplate>
</asp:TemplateField>
As Figure 11 shows, the end result is not that pretty as the “View” column is still displayed
even though the “View” LinkButtons within the column are hidden. We will look at how to
hide the entire GridView column (and not just the LinkButton) in the next section.
Figure 11: The LoginView Control Hides the “View” LinkButtons for Anonymous
Visitors
1. Determine whether the user visiting the page can access the functionality, and
2. Programmatically modify the user interface based on whether the user has access to
the functionality in question.
To demonstrate the application of these two tasks, let’s only allow Tito to delete files from
the GridView. Our first task, then, is to determine whether it is Tito visiting the page. Once
that has been determined, we need to hide (or show) the GridView’s Delete column. The
GridView’s columns are accessible through its Columns property; a column is only rendered
if its Visible property is set to True (the default).
Add the following code to the Page_Load event handler prior to binding the data to the
GridView:
<PrincipalPermission(SecurityAction.Demand, Authenticated:=True)> _
Protected Sub FilesGrid_SelectedIndexChanged(ByVal sender As Object, ByVal e
As System.EventArgs) Handles FilesGrid.SelectedIndexChanged
...
End Sub
<PrincipalPermission(SecurityAction.Demand, Name:="Tito")> _
Protected Sub FilesGrid_RowDeleting(ByVal sender As Object, ByVal e As
System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles
FilesGrid.RowDeleting
...
End Sub
The attribute for the SelectedIndexChanged event handler dictates that only authenticated
users can execute the event handler, where as the attribute on the RowDeleting event
handler limits the execution to Tito.
If, somehow, a user other than Tito attempts to execute the RowDeleting event handler or
a non-authenticated user attempts to execute the SelectedIndexChanged event handler,
the .NET runtime will raise a SecurityException.
Figure 14: If the Security Context is not Authorized to Execute the Method, a
SecurityException is Thrown
Note: To allow multiple security contexts to access a class or method, decorate the
class or method with a PrincipalPermission attribute for each security context.
That is, to allow both Tito and Bruce to execute the RowDeleting event handler, add
two PrincipalPermission attributes:
<PrincipalPermission(SecurityAction.Demand, Name:="Tito")> _
<PrincipalPermission(SecurityAction.Demand, Name:="Bruce")> _
In addition to ASP.NET pages, many applications also have an architecture that includes
various layers, such as Business Logic and Data Access Layers. These layers are typically
implemented as Class Libraries and offer classes and methods for performing business logic-
and data-related functionality. The PrincipalPermission attribute is useful for applying
authorization rules to these layers.
The URL authorization framework applies authorization rules on a page-by-page basis. With
URL authorization, either the requesting identity is authorized to access a particular
resource or not. Many scenarios, however, call for more fine grain authorization rules.
Rather than defining who is permitted to access a page, we may need to let everyone
access a page, but to show different data or offer different functionality depending on the
user visiting the page. Page-level authorization usually involves hiding specific user interface
elements in order to prevent unauthorized users from accessing prohibited functionality.
Additionally, it is possible to use attributes to restrict access to classes and execution of its
methods for certain users.
Happy Programming!
Further Reading
For more information on the topics discussed in this tutorial, refer to the following
resources: