Programmer to ProgrammerTM | |||||
|
|
|
|
|
|
|
|
|
|
|
| |||||||||||||||||||
The ASPToday
Article March 1, 2002 |
Previous
article - February 28, 2002 |
||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
ABSTRACT |
| ||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
Article Discussion | Rate this article | Related Links | Index Entries | ||||||||
ARTICLE |
This article is part of the ASPToday Security Week, 25th February - 1st March 2002.
"Single login" is the rallying cry of all user-interface experts (not to mention frustrated end users). The idea is that once your users are authenticated to a trusted system, you should not have to interrogate them for credentials every time they attempt to access a secured resource - any resource - including your application.
So how do you achieve single login within your ASP.NET application? Well the quick answer is by using integrated Windows security. By using the Windows authentication provider, you ensure that your user's Windows logon credentials are always available to your application. But knowing that your user is a valid Windows user is seldom enough. Most likely you will want to secure parts of your application according to roles (or Groups as they are known in Active Directory). This is simple enough to do, either by customizing your application's Web.config files or with methods available to you via the WindowsPrincipal object (more on the WindowsPrincipal object later).
But what if you would like to go further? What if once you know who your users are you also would like to obtain their preferences (such as language or view settings)? Or, what if your application requires roles that can't or shouldn't exist within your organization's Active Directory structure? Of course you could always grab this customized user-specific information within the logic of your ASP.NET pages themselves. You could even go so far as to encapsulate this logic in a class or function accessible globally within your project, but a much more elegant solution exists. ASP.NET provides a mechanism by which you can blend your own customized personalization and membership solution with Integrated Windows security (or any authentication provider) and make that information available to every ASP.NET page within your application "automagically."
In this article I will illustrate a technique for:
In order for the code samples in this article to work you will need to have IIS 5.0 and the .NET framework properly installed on your machine. It is assumed that the reader has a working knowledge of basic security concepts, the .NET framework, C# and ASP.NET.
Authentication is the process of finding out who your user is. Once you know who they are you can make decisions about what they are allowed to do (this decision-making process is referred to as authorization) as well as associate user-specific information to their requests. Users prove their identity to a system by providing identification credentials. Usually these identification credentials consist of a username and password but they could be anything - even biometric data like a fingerprint or a retinal scan. The idea of single logon mandates that once users are authenticated to a trusted authority you should use those credentials to identify users in your own system as well.
The decision of which authentication provider to trust depends on many factors, but Microsoft Windows is a great choice in many situations. In most corporate environments users must log on to a Windows domain in order to gain access to any network resources, so you are guaranteed to have their credentials available when they attempt to access your application. This makes the Windows authentication provider a good choice for the vast majority of corporate intranet projects.
It should be noted however that Windows authentication is not ideal in all situations. First of all, if you must support Netscape, integrated Windows authentication is not an option. Integrated Windows authentication only works with Internet Explorer. You will also find that if you have a large number of users (hundreds of thousands or millions for example), you will exhaust the limit of Active Directory's performance capabilities. If your authentication needs are this intense, you will be forced to authenticate your users via some other method (most likely against a customized database scheme). Also, if your users aren't on your corporate network, Windows authentication will not provide the single logon functionality that you are looking for because your users will have to log on to your network (or face the NT challenge provided by IE) in order to use your application. Finally, if all of your users do not have a valid Windows account, then integrated Windows authentication obviously won't fulfill your needs.
Despite the limitations of Windows authentication, it should be noted that the techniques used in this article are applicable to any authentication provider.
ASP.NET makes integrating Windows authentication a snap. You select your authentication provider using the Web.config file. The settings for windows authentication are as follows:
<authentication mode="Windows"/>
The above entry is all that you need to ensure that your users' Windows identity is available to all of your ASP.NET pages. Of course, IIS does the least amount of work possible when authenticating your users. This means that if you allow anonymous access within IIS and NTFS access control lists don't prevent anonymous access to your application's resources; the web server will skip the hard work of figuring out exactly who your users are and will report that everyone accessing your site is an anonymous user! In the vast majority of cases, this is not what you will want, so the next step in securing your ASP.NET application is to turn off anonymous access to your site. To disallow anonymous access to your application, follow these steps:
Note: This screenshot is taken from Windows 2000. The XP version looks slightly different.
User authentication and authorization in .NET are handled primarily via Identities and Principals. You will find the basic Identity and Principal classes in the System.Security.Principal namespace. An Identity object tells you who the user is. Identity objects implement to the IIdentity interface and as a result contain the following properties:
AuthenticationType | Gets the type of authentication used |
IsAuthenticated | Gets a value that indicates whether the user has been authenticated |
Name | Gets the name of the user |
A Principal object is a combination of an Identity object and its associated roles. This additional information is usually used to determine what a user has permission to do (a process known as authorization). Principal objects conform to the IPrincipal interface. As a result, all Principal objects must contain (at the very least) a property exposing their associated Identity, as well as a method called IsInRole. The IsInRole method can be called to determine whether or not a user has a particular role (or in Active Directory parlance, whether or not they are the member of a particular group).
All ASP.NET pages (objects of type System.Web.UI.Page) contain a User property. This property returns a Principal object that represents the user making the current request. This object can be of any type as long as it implements the IPrincipal interface. This is an important point to remember as we'll be making use of it later to create our own customized user object and attach it to our application context.
By default, when you use windows authentication the User property of your ASP.NET pages is populated with a WindowsPrincipal object. As you might remember, all Principal objects contain an Identity property. In the case of the WindowsPrincipal object, this property returns a WindowsIdentity object. The WindowsIdentity object contains quite a few useful properties and methods, a few of which are listed below:
IsAnonymous | Gets a value indicating whether the user account is identified as an anonymous account by the system. |
IsAuthenticated | Gets a value indicating whether the user has been authenticated by Windows. |
IsGuest | Gets a value indicating whether the user account is identified as a Guest account by the system. |
IsSystem | Gets a value indicating whether the user account is identified as a System account by the system. |
Name | Gets the user's Windows logon name. |
Token | Gets the Windows account token for the user. |
GetAnonymous | Returns a WindowsIdentity object that represents an anonymous Windows user. |
GetCurrent | Returns a WindowsIdentity object that represents the current Windows user. |
Impersonate | Overloaded. Allows code to impersonate a different Windows user. |
Using the WindowsPrincipal object you can determine if a user is in a particular Windows Group. For example, the following code tests to see if the user is a member of the local machine Administrators group and sends the result to the browser (of course you must be using Windows authentication in order for this to work). If the user is in the group, it will print "true"; otherwise it will print false:
Response.Write(User.IsInRole(@"BUILTIN\Administrators").ToString());
In order to implement IPrincipal you must provide the implementation for the IsInRole method. The following is a Customized User object that effectively wraps any Principal and Identity information passed to it during construction and also exposes preference information read from an XML resource file called Preferences. In our example we will pass Windows Principal/Identity information into the object during construction, but it could just as easily be Principal/Identity information from Passport, a database or some other authentication provider.
using System; using System.Resources; using System.Security.Principal; namespace BlendedSecurity { public class CustomUser : IPrincipal { protected string mstrPreferences; protected ResourceManager mobjManager; protected IPrincipal mobjPrincipal; public string Preferences { get { //Get the preferences stored in the resource file //for the user with the name given mstrPreferences = mobjManager.GetString(mobjPrincipal.Identity.Name); return mstrPreferences; } } //Identity Property. Returns the public IIdentity Identity { get{return mobjPrincipal.Identity;} } // Constructor public CustomUser(IPrincipal robjPrincipal) { //Get resource manager for the resource file mobjManager = new ResourceManager("BlendedSecurity.Preferences", typeof(CustomUser).Assembly); mobjPrincipal = robjPrincipal; } public bool IsInRole(string vstrRoleName) { //Use the IsInRole method of the Principal //object stored internally return mobjPrincipal.IsInRole(vstrRoleName); } } }
The key to blending your own custom personalization and membership solution with integrated Windows authentication is replacing the IPrincipal object returned by the page's User property with one of your own creation. You can do this quite easily within your application's global.asax file. You can catch the authentication event fired when a Windows user is authenticated to your application by handling the WindowsAuthentication_OnAuthenticate event handler. This is where you will want to replace the Application Context's User object with your own. I'll be using the custom user object just described above:
public void WindowsAuthentication_OnAuthenticate(Object sender, WindowsAuthenticationEventArgs e) { //Create a WindowsPrincipal object from the WindowsIdentity object //contained in the Windows Authentication Event Arguments WindowsPrincipal objPrincipal = new WindowsPrincipal(e.Identity); //Create your customized user object from the WindowsPrincipal CustomUser objUser = new CustomUser(objPrincipal); //Attach the custom user object to the application context Context.User = objUser; }
Once you've attached your custom User object to your application context in global.asax , it is available to every ASP.NET page within your application. In order to access the custom properties and methods available on your object you will have to cast the object returned by the User property of the ASP.NET page to your custom object type. This step is necessary because the User property returns the IPrincipal interface to your object. The following code snippet shows a page that accesses our custom user object in the Page_Load method:
private void Page_Load(object sender, System.EventArgs e) { //The global.asax has taken care of creating your customized //user object and attaching it to the application context. //Now you must cast the User object to the CustomUser type so //that you can access its enhanced abilities! CustomUser objUser = User as CustomUser; string strPreferences = objUser.Preferences; if (strPreferences == null) strPreferences = "Unable to retrieve Preferences for " + objUser.Identity.Name; strPreferences = "<h2>Preferences for " + objUser.Identity.Name + "</h2>" + Server.HtmlEncode(strPreferences); Response.Write(strPreferences); }
In our simple example, the CustomUser object doesn't hold any expensive shared resources (such as database connections) that must be released once you are done with them. But in a real-world application your user object may very well need access to such resources in order to populate themselves with user-specific data. In this case, you will need to implement the IDisposable interface on your object. But when do you call it? A good time to dispose of resources tied up by your user object is in the Application_EndRequest event handler of global.asax. For example:
protected void Application_EndRequest(Object sender, EventArgs e) { CustomUser objUser = this.User as CustomUser; objUser.Dispose(); }
The above code ensures that the Dispose() method for your User object is called whenever a request is finished.
The code snippets you've seen in this article are taken from the BlendedSecurity Sample Application. This sample application uses integrated Windows authentication to determine a user's identity and then combines that information with preferences data obtained from a resource file. The customized user object containing the user's blended data is then attached to the request's application context using the techniques described in this article. The Preference data is then displayed on screen:
The code provided with this article is packaged in a zip file. Unzipping the file will place the necessary files in a directory called BlendedSecurity. Next you will have to:
Of course in the real world your user preferences would never be persisted in an XML resource file, but hopefully this example will give you the tools you need to apply these techniques to your data store of choice. Whatever the data store you choose, however, keep in mind that you will need to implement some mechanism for keeping it synchronized with the users that exist in your authentication provider. This will usually take the form of the typical add/update/delete user admin screens that you would normally provide in an application, but in this case they will be pulling data from your authentication store as a part of performing their respective services.
Attaching customized user objects to your application context is a great way to solve the single logon dilemma. The capabilities of your customized user object are limited only by your imagination. It's a great place to store user preferences, permissions and other user-specific information. Once more, these powerful customized User objects are instantly and seamlessly available to every page in your ASP.NET application. This is a tremendous win-win for both end users and developers!
How ASP.NET Security Works
The Windows Authentication Module Provider
Principal and Identity Objects
Article Information | |
---|---|
Author | Akil Franklin |
Technical Editors | John R. Chapman, Vickie Pring |
Project Manager | Helen Cuthill |
Reviewers | Sean M. Schade, John Boyd Nolan |
If you have any questions or comments about this article, please contact the technical editor.
|
| |||||||
| |||||||||||||||
|
ASPToday is brought to you by
Wrox Press (http://www.asptoday.com/OffSiteRedirect.asp?Advertiser=www.wrox.com/&WROXEMPTOKEN=77785ZrPepuudtuWA3eiCODNXJ).
Please see our terms
and conditions and privacy
policy. ASPToday is optimised for Microsoft Internet Explorer 5 browsers. Please report any website problems to webmaster@asptoday.com. Copyright © 2002 Wrox Press. All Rights Reserved. |