Apply ASP.NET Authentication and Authorization Rules to Static Content with IIS 7.0's Integrated Pipeline Feature
By Scott Mitchell
Introduction
Many ASP.NET applications that support user accounts use forms-based authentication and URL authorization. Forms-based authentication
is a mechanism by which users can log into the site by entering their credentials - typically a username and password - into textboxes in a login page.
Once validated, the server returns an authentication ticket cookie that identifies the request; this cookie is sent back on subsequent visits
to the site, which keeps the user "logged on" for the lifetime of the cookie or until the authentication ticket expires. URL authorization is a mechanism
by which authorization rules can be defined on a URL-by-URL or folder-by-folder basis. In short, with URL authorization you can instruct the application
to deny access to a particular folder to anonymous users, or prohibit access to a particular URL to all users except those in the Admin role.
Because the forms-based authentication and URL authorization features are part of ASP.NET, the ASP.NET runtime must be consulted in order for these
features to be employed. When developing an application using Visual Studio and the ASP.NET Development Server, all requests are handed off to
the ASP.NET runtime. Consequently, the authentication and authorization logic applies to both ASP.NET-specific content like ASP.NET pages and
Web Services, as well as static content like images, PDFs, and ZIP files. Things are a bit different with IIS,
Microsoft's production-grade web server software. By default, IIS handles all static content itself, only invoking the ASP.NET runtime when an
ASP.NET resource is requested. As a result, those URL authorization rules you apply to static content are ignored when hosting your site in a production
environment!
The good news is that starting with IIS 7.0 Microsoft has introduced the integrated pipeline. In a nutshell, with the integrated pipeline you
can instruct IIS to work with the ASP.NET runtime during the processing of all requests, regardless of whether the request is for an ASP.NET-specific
resource. This article shows how to use the integrated pipeline feature to apply ASP.NET forms-based authentication and URL authorization rules to
static content. Read on to learn more!
A Simple Forms-Based Authentication and URL Authorization Example
To demonstrate the differences between the ASP.NET Development Server and IIS when serving static content I created a simple demo web application that
is available for download at the end of this article. The demo website is a mock photo album site that supports user accounts and authenticates users
via forms authentication. This configuration is specified in the root folder's Web.config file's
<authentication> section:
In addition to specifying that the site uses forms-based authentication, I've also included three hard-coded user accounts in the
<credentials> section. Typically, the user account information
would be stored in a database, but to simplify this demo I just used ASP.NET's facility for storing credentials directly in Web.config.
For more information on this technique, as well as a more thorough look at forms-based authentication, be sure to read
Darren Neimke's article Using
Forms Authentication in ASP.NET.
The image gallery's ASP.NET pages and images are only accessible to authenticated users. I've placed all of these gallery-related files in the
~/Gallery folder, and added a Web.config file to that folder with a URL authorization rule that denies anonymous users
(thereby allowing only authenticated users):
The ~/Gallery folder contains both ASP.NET pages (such as ~/Gallery/Default.aspx) and image files (such as
~/Gallery/Images/Sam.jpg). The idea is that any file in the ~/Gallery folder - including static image files - should
only be accessible to authenticated users. If an anonymous user attempts to visit an ASP.NET page in the ~/Gallery folder or view an
image in the ~/Gallery folder then they should be automatically redirected to the login page (~/Login.aspx).
Examining the Behavior of the ASP.NET Development Server
When you launch a web application from Visual Studio the ASP.NET Development Server is started and the web application's pages are served through
the ASP.NET Development Server, by default. The ASP.NET Development Server is a lightweight web server that only allows testing through localhost.
It is oftentimes easier to use than IIS because it requires no setup or configuration.
Unlike IIS, the ASP.NET Development Server routes all incoming requests to the ASP.NET runtime. It doesn't matter whether an ASP.NET resource
such as an ASP.NET page is requested, or if a static resource like an image is requested. In either case the ASP.NET Development Server dispatches
the request to the ASP.NET runtime which proceeds through its various pipeline stages, which includes authentication and authorization. Consequently,
if an anonymous visitor coming through the ASP.NET Development Server attempts to directly visit an image in the ~/Gallery folder they are not shown
the image and instead redirected to the login page, as the following screen shot shows:
View the full size image and check out the URL in the browser's Address
bar in the screen shot above. Note that the Address is the login page (Login.aspx) but the querystring includes a ReturnUrl parameter
that points back to the file the user initially requested (Sam.jpg). Once the user successfully authenticates on the login page they
will automatically be redirected to the picture of Sam, which is visible
now because they are no longer an anonymous user.
The ASP.NET Development Server implements the behavior we want - enforcement of the URL authorization rules specified for the ~/Gallery
folder for dynamic and static content alike.
Examining IIS's Default Behavior
By default, the IIS web server dispatches requests for ASP.NET resources to the ASP.NET runtime, but handles requests for static content itself. As a result,
requests for static content are served without regard to the URL authorization rules spelled out in the ASP.NET application's configuration.
I have IIS 7.0 installed on my computer and have added a new IIS Application that points to the directory where the demo application resides. If I
visit this website through IIS rather than through the ASP.NET Development Server I can view "protected" static content such as Sam.jpg.
The following screen shot was taken after visiting the site through IIS as an anonymous user and typing in the URL to Sam.jpg. Unlike
the example above, I am shown the picture rather than taken to the login page.
This is not the behavior we want, as it allows anonymous users to potentially see private content (if they know the direct URL). All it would take it
one of the users on the site to unwittingly publish a direct link to the image on his blog or website and the search engines would pick it up and
the picture would be revealed to the world at large. The good news is that we can configure IIS 7.0 to use the integrated pipeline feature,
which causes IIS to involve the ASP.NET runtime when handling requests for static content.
Using IIS 7.0's Integrated Pipeline
IIS applications are serviced by Application Pools, sometimes called AppPools. In IIS 7.0 these Application Pools can be configured to use either the classic
pipeline or the integrated pipeline. The classic pipeline does not work with the ASP.NET runtime; if this option is set then IIS will continue to server
static requests on its own without ASP.NET integration. Therefore, the first step in protecting static content in our demo application is to ensure that
the AppPool used by our IIS application is configured to use the integrated pipeline.
If you use a web hosting company and are on an IIS 7.0 web server then you likely have an option in your Control Panel where you can specify what
pipeline is used. Contact your web host provider for more information. If you have direct control over your web server, launch the IIS Manager and select
the Application Pools option from the list on the left. Double-click the Application Pool used by your IIS application and ensure that the Integrated Pipeline
option is enabled.
Now that we have instructed IIS to use the integrated pipeline the last step is to plug in the ASP.NET modules into the pipeline.
Namely, we need to instruct IIS 7.0 to use ASP.NET's FormsAuthenticationModule and UrlAuthorizationModule while processing
requests. Add the <sytem.webServer> element to the web application's
root Web.config file, remove the existing definitions for the FormsAuthenticationModule and UrlAuthorization modules
and add them back in using ASP.NET's versions (namely,
System.Web.Security.FormsAuthenticationModule
and System.Web.Security.UrlAuthorizationModule).
Once this configuration information has been specified in Web.config revisit the Sam.jpg image file hosted via IIS from your browser.
This time while processing the request for the image file IIS allows for the ASP.NET runtime to perform authentication and authorization logic. The
ASP.NET runtime notes that the request is coming from an anonymous user and is for a URL that denies anonymous users. Therefore, the anonymous user is
redirected to the login page, just like when visiting the image file served by the ASP.NET Development Server.
Conclusion
The IIS and ASP.NET Development Server web servers handle requests differently. The ASP.NET Development Server dispatches all incoming requests
to the ASP.NET runtime; however, by default IIS does not work with the ASP.NET runtime when static content like images, ZIP files, and PDFs are requested.
By bypassing the ASP.NET runtime for static content, any URL authorization rules specified in ASP.NET are ignored. As a result, it is possible for
unauthorized users to view static content that resides in folders that are protected via ASP.NET's URL authorization.
In IIS 7.0 you can remedy this by using the integrated pipeline feature and instructing the IIS pipeline to work in tandem with the ASP.NET
modules that handle authentication and authorization logic. This article shows how to enable the integrated pipeline in IIS 7.0 and how to configure
the web application to use the ASP.NET-related modules in the pipeline. For more information on IIS 7.0's integrated pipeline refer to the articles
in the Further Reading section below.