Practical ASP.NET
Customize Security in ASP.NET 2.0
ASP.NET 2.0 provides a robust set of functionality for implementing rich and flexible forms authentication in your applications.
ASP.NET makes it easy to configure security for your site. But integrating your own code, centralizing your user information, sharing security settings across multiple sites, and integrating security with ASP.NET 2.0's menuing controls requires some additional work.
Technology Toolbox: Visual Basic, ASP.NET
ASP.NET 2.0 offers you better ways to manage folder security, manage user security information, and integrate custom code into the login process than was possible in ASP.NET 1.1.
That said, ASP.NET 2.0 security doesn't provide you with everything you need to implement a complete solution. For example, implementing a robust security system for your ASP.NET-based apps also requires using HTTPS. Therefore, you'll want to customize the default settings for most environments. Otherwise, each Web site has separate security settings, which prevents you from sharing login information across multiple sites and forces you to manage security in each individual site. You would also have to choose between locking out users that have turned off cookies, or abandoning the ASP.NET 2.0 menu system if your app depends on the default settings.
Fortunately, getting around these limitations isn't difficult. It simply requires that you give a little thought to the goals you're trying to accomplish, and then implement custom code that lets you step around the specific limitations of a default security implementation.
ASP.NET 2.0 keeps user-related information (user ids, passwords, and so on) in a SQL Server database by default. Visual Studio 2005 generates this database (named ASPNETDB.MDF) for you with the Web-based configuration tool that comes with Visual Studio 2005 and places it in your site's App_Data folder (see Figure 1).
Using this default configuration means that every one of your sites will have its own security database with a unique set of users. You might prefer to manage your users in a central location, rather than have to switch from one Web site to another to add or remove users. If you do prefer to keep all of your user information in a single location, you need to define a new security provider in your site's Web.config file and aim that provider at some central copy of ASPNETDB.MDF.
Fortunately, doing this isn't difficult. First, add the connection string for your central ASPNETDB.MDF file to the connectionStrings element in each site's Web.config file:
<configuration>
<connectionStrings>
<add name="CentralSecurity" connectionString=
" Data Source=
.\SQLEXPRESS;AttachDbFilename=
C:\Websites\SharedSecurity App_Data\ASPNETDB.MDF;
Integrated Security=True;User Instance=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
Next, use the membership element to define a new provider within the new system element. Then, use the add tag within the provider's element to define a new provider that uses the connection string you've already defined. Note that in addition to the connection string, the add tag must specify the type of the provider (use System.Web.Security.SqlMembershipProvider). Finally, you must assign your provider a name and use that name in the defautProvider attribute of the membership element so that your site will use your new provider:
<system.web>
<membership defaultProvider="SharedSecurity">
<providers>
<add name=" SharedSecurity "
type=
"System.Web.Security.SqlMembershipProvider"
connectionStringName="CentralSecurity"
applicationName="NorthwindCRM" />
</providers>
</membership>
Share User Info Across Sites
You can omit the applicationName attribute, but including it allows you to share user information across sites. By default, each site uses a different value for applicationName. This allows ASP.NET to keep each site's user information separate. But you can set the applicationName to the same value in the Web.config file for all of your sites to share user information across sites.
A warning: Set up your provider in the Web.config file first, if you intend to share users across among your Web sites. This will enable you to set the applicationName attribute. Omitting the applicationName attribute causes ASP.NET to auto-generate an applicationName for your site, which will be awkward to match in other sites.
If you prefer to keep your security information in a database of your own choosing, you can add the necessary tables to a SQL Server database using the Aspnet_regsql.exe utility (which comes with ASP.NET) and pass it the path to your database. Then you can setup a provider, as in the example already shown, but first you should set the configuration string to point to your database.
After a user logs on successfully, ASP.NET generates a cookie to track the user's login status. The contents of the cookies are encrypted to prevent interference by default. Beware, it's possible for someone to hijack a cookie from a client after the user has successfully logged on, and spoof your site. You should implement HTTPS for your site to protect yourself fully from this kind of unauthorized access.
Another danger is that users will get up and walk away from their computers while logged onto your site. You can control how long the security cookie is valid and whether the cookie is renewed each time the user requests a new page by setting the slidingExpiration and timeout attributes on the forms tag. These settings ensure that the cookie is only valid for 10 minutes after the user has logged on:
<forms loginUrl="login.aspx"
slidingExpiration="false"
timeout="10"/>
One side effect of using cookies to implement security is that users who have turned off cookies won't be able to access your secured site. Set the loginURL attribute on the forms tag to "UseUri"; this enables you to use URL rewriting to store security information:
<forms loginUrl="login.aspx"
cookieless="UseUri"/>
Don't be seduced into using the AutoDetect option for the cookieless attribute. AutoDetect switches between using cookies and URL rewriting based on what the client is capable of, not what the client is configured for. For example, assume a page on your site is requested by a client that is unable to accept cookies, as is the case with browsers on some kinds of PDAs. ASP.NET inserts security information into the URL. However, AutoDetect doesn't turn on URL rewriting, if the client is capable of accepting cookies and the user has turned cookies off.
Cookieless sessions don't work with the Menu and TreeView controls that are part of the ASP.NET 2.0 toolkit. The URLs that are generated for the hyperlinks in these controls simply don't include the security information, at least at this time. The code download for this article includes the source code for a provider called RelativeSiteMapUrl that supports URL rewriting for the menu controls. All you need to do is to drop the file into your site's App_Code folder and set the siteMap tag's defaultProvider to the name of the provider:
<siteMap defaultProvider="RelativeSiteMapUrl">
Set Up Folder Rules
User-security information is kept in a database, but access rules for the folders in your site are kept in the site's Web.config file. As with ASP.NET 1.1, you control who has access to folders in your site in ASP.NET 2.0 by adding allow/deny tags to the authorization element in a folder's Web.config file. The default setting allows all users access to the folder, so the first entry you want to make to the authorization element is to add an element that will either deny all users (use "*") or deny users who haven't logged on (use "?").
ASP.NET processes allow/deny tags from the top, down. This means that ASP.NET rejects or allows the user as soon as ASP.NET finds an allow or deny tag that matches the currently logged-on user. After you add the general deny tag, you should add the allow or deny tags for specific roles and users higher in the list than the general deny tag. This example denies those users logged on as "visitor," but allows the specific users, "Sue" and "Mary" :
<authorization>
<deny role="visitor" />
<allow users="Sue, Mary"/>
<deny users="*"/>
</authorization>
Note that the users Sue or Mary will not be allowed to access the site if they are also members of the visitor role. ASP.NET will reject all members of the visitor role before testing the user against the subsequent allow tag.
You don't control your entire site's security using the settings in the Web.config file for a subfolder. Subfolders automatically inherit the settings of their parent folders. This can make determining the security for any particular folder difficult to determine since looking just at a Web.config file for a subfolder gives you only part of the story.
There are two solutions for this problem. First, you can use the ASP.NET configuration tool to view all the access rules for a folder by selecting the Manage Rules option, and then selecting the specific folder you want to view information about (see Figure 2). A second is approach is to set all the access rules for all of your folders from the Web.config file in your site's root folder by using the location element. The location element lets you put multiple system.web elements in your root's Web.config file; it also lets you specify the folder that each system.web element applies to in the location tag's path attribute.
This example turns on Forms authentication for the root folder and denies access to users who haven't logged on yet. The second location element denies access to the SalesOrder folder to logged-on users, except for those with the Sales role (see Listing 1).
Using locations doesn't give you a display that's as easy to read as the configuration tool's display, but it does bring all of your rules together into one place.
Integrate Custom Code
You still need to provide your users with a way of identifying themselves and gaining access to your site. The easiest solution is to drag the ASP.NET Login control onto your page (see Figure 3).
This can work, but you probably want to incorporate your own code into the login process, as well. For example, you might want to provide an audit record of login attempts. You might also want to replace the authentication method that ASP.NET provides with your own custom authentication code.
You customize the Login control by adding code to the control's Authenticate event. Set the Authenticated property of the parameter passed to the event to True when you authenticate the user successfully. Note that authentication is easy: Simply call the ValidateUser method of ASP.NET's Membership object, passing a user name and password. The method returns True if a user is found in the security database with a matching password.
This template for the Authenticate event allows you to add your own code at several points in the Authenticate process:
Protected Sub Login1_Authenticate(ByVal sender _
As Object, ByVal e As _
WebControls.AuthenticateEventArgs?)
'?pre-login tasks?
If System.Web.Security.Membership.ValidateUser( _
Me.Login1.UserName, Me.Login1.Password) Then
' ? successful login tasks?
e.Authenticated = True;
Else
' ?unsuccessful login tasks?
End If
' ?post-login authentication tasks?
End Sub
You can also replace the provided ValidateUser method with your own authentication routine and call it from within the Authenticate method, if you want to implement a custom-authentication method.
Adding an empty event to a form typically falls into the "no harm, no foul" category of programming errors. However, you must add code to set the Authenticated property if you do add an Authenticate routine to your form. The Authenticated property defaults to False, so having an empty Authenticate event guarantees that no user will ever be able to log on. This gives you an extremely secure site, but one with rather low user satisfaction.
As is often the case with .NET, there's much more in this solution for you to explore on your own. The ASP.NET configuration tool updates the security database using objects that are available in the .NET framework. You can use those objects from your own code to dynamically add new users, reset passwords, and perform other security-related tasks. Also, the Login control has a multitude of properties that allow you to customize its appearance; the Login control is only one of the login-related controls that you can use in ASP.NET 2.0. For example, the LoginView control displays different content to users in different roles automatically. The PasswordRecovery and ChangePassword controls allow you to add common security-related features to your site simply by dragging a control onto a form.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.