Once you've authenticated a user (that is, determined that you know who the user is), you may want to restrict or deny access to various parts of your site, or to various activities on your site, based on the user's identity. This process is called authorization. As an example, you might want to allow supervisors and managers to access the Maintenance menu on your site but hide the menu from normal users. ASP.NET provides several techniques for managing authorization in your applications. This section investigates three different issues:
Controlling authorization using Web.config
Managing authorization dynamically
Taking advantage of role-based authorization
You can use the authorization element in Web.config to determine exactly which users can enter your site. To try out this technique, follow these steps:
In the Solution Explorer window, double-click Web.config to load the file into the code editor window.
Modify the authorization element so that it looks like this:
<authorization> <deny users="?" /> <deny users="Fuller" /> </authorization>
Run the application.
Attempt to log in as Fuller (password 3457) and verify that you cannot log in.
Attempt to log in using any other valid user ID/password combination from Table 24.1 and verify that you can log in successfully.
Because you added Fuller to the "deny" list, you won't be able to log in as that particular employee.
Although you can place names into the "deny" list in the Web.config file in the same folder as your application, it's not a likely scenario you can just as easily control authorization directly in the login page you created earlier. If you create a separate virtual root for a specific "protected" section of your site (the maintenance section, for example), you could create a new Web.config file and place it into that root. Then, when ASP.NET attempts to load any page within that root, it will use the Web.config file it finds in that folder and then allow or deny users based on the Web.config file it finds there. Using separate Web.config files in individual folders allows you to maintain complete control over who you allow into various parts of your application.
Managing Authorization Dynamically
The Page object provided by the ASP.NET page framework provides the User property, which in turn provides the Identity property. This property allows you to find out the identity of the currently logged-in Windows user. The User property provides properties such as Name, IsAuthenticated (to determine whether the user is currently authenticated), IsInRole (to determine whether the current user is in a specified role), and more. If you wanted to take specific action based on the current logged-in user, you might modify the code in Main.aspx to look like this (not in the sample project):
Private Sub Page_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Dim blnShow As Boolean blnShow = (Page.User.Identity.Name = "Davolio") lnkLogOut.Visible = blnShow hypEmpMaint.Visible = blnShow hypPwdChange.Visible = blnShow End Sub
Just as you can work with users and roles in Windows, you can take advantage of the same technology to authorize users from within your ASP.NET applications. That is, you can authorize a user to take some action or view some information, based on the role the user has taken on administrator or manager, for example.
To understand the use of roles in ASP.NET, you'll need to dig into a few concepts first.
In order for ASP.NET to be able to work with the various .NET classes that allow for authorization, the .NET Framework provides two important interfaces that other objects implement: IIdentity and IPrincipal.
When we say that one class "implements an interface," we're referring to the object-oriented technique called interface inheritance. One class (for example, IIdentity) defines the set of properties, methods, and events that are required by any class that claims to contain identity information. Given that class, any other class can implement the behavior defined by the interface in any way that's required. Although this sounds similar to inheriting from a base class, the two techniques are different enough that there are good reasons to use one or the other. For more information, you'll need to find a good article or book on object-oriented programming techniques.
An object that implements IIdentity represents the current user on whose behalf the code is running. There are four .NET classes that implement this interface:
GenericIdentity. A basic implementation of IIdentity that can represent any user.
FormsIdentity. Represents a user who has been authenticated using forms-based authentication. This object provides a way for an application to interact with the cookie-authentication ticket.
PassportIdentity. Represents a user who has been authenticated using Passport authentication. This class provides access to the Passport profile cookies.
WindowsIdentity. Represents a user who has been authenticated using Windows authentication (in other words, a Windows user).
Each of these classes provides its own functionality, depending on its use. For example, the WindowsIdentity class provides information on whether a user is logged in to a System, Guest, or Anonymous account; the PassportIdentity class provides properties that allow you to interact with its Passport-authentication ticket.
An object that implements the IPrincipal interface represents the security context of the user on whose behalf the code is running. This context contains identity information (in the form of an object that implements IIdentity) and any roles to which the user belongs.
Two classes implement the IPrincipal interface:
GenericPrincipal. This class simply allows code to check the role membership of a user represented by the GenericPrincipal object. If you use this class, you'll need to create the roles and add users to it yourself it doesn't communicate with or have information about Windows roles.
WindowsPrincipal. This class allows code to check the Windows role membership of the user represented by the WindowsPrincipal object.
Using Identities and Principals
Suppose you'd like to be able to assign users to particular roles (Users or Managers) and then be able to show or hide information and controls based on the role the current user is in. To do this, you'll need to work with some class that implements IIdentity (representing the user) and some class that implements IPrincipal (representing the user's security context).
In this example, you'll examine the identity of each user who logs in to your site, and based on the username (remember, you're only allowing in employees of the Northwind company) you'll add some employees to the Managers role and others to the Users role. Then, based on the role of the user, you'll show or hide controls on the main page.
Yes, you're unlikely to hard-code this information in a real application. You might, instead, use the Windows roles and the WindowsPrincipal class to work with the users. Alternatively, you might store information about the users' roles in your company's database. To keep things simple here, we're using the GenericIdentity and GenericPrincipal classes (although we could very easily have used the FormsIdentity object, because we know that in this application, we're using forms-based authentication to authenticate the user).
Normally, to work with identities and principals, you'll need to first create an object that implements IIdentity (one of GenericIdentity, FormsIdentity, and so on), supplying login information to identify the authentication ticket for the user. You'll then use the new IIdentity object, along with an array of strings containing the names of the roles assigned to the user, to create an object that implements IPrincipal (either GenericPrincipal or WindowsPrincipal).
You'll see code like this, later in this section:
Dim astrRoles(0) As String Dim gid As GenericIdentity Dim gp As GenericPrincipal ' Create Identity gid = New GenericIdentity(LoginID) ' Create Principal astrRoles(0) = "Managers" gp = New GenericPrincipal(gid, astrRoles)
This example creates a new GenericIdentity object, given the login ID supplied by the user. The code sets the only role for this user (Managers) and then creates a new GenericPrincipal object, passing in the GenericIdentity object and the roles. This GenericPrincipal object identifies the security context for the logged-in user, including the login information and the roles.
Adding Support for Role-Based Authorization
| || |
To test out role-based authorization, this section walks you through inserting code to add role information to a GenericPrincipal object. You'll also see how to create the appropriate GenericIdentity object, corresponding to the logged-in user, and how to retrieve the security information when necessary.
In this example, the goal is to show the Employee Maintenance and Change Password links only if the logged-in user is a member of the Managers role. Otherwise, those links simply shouldn't appear on the page.
Follow these steps to add role-based authorization support to Login.aspx and Main.aspx:
In the Solution Explorer window, select Login.aspx. Right-click and select View Code from the context menu.
Scroll to the top of the file and add the following Imports statement:
Add the following procedure to the class:
Private Sub SetIDAndPrincipal( _ ByVal LoginID As String) ' This must be an array, but since you ' know you're only dealing with a single role, ' declare it to contain only the 0th element. Dim astrRoles(0) As String Dim gid As GenericIdentity Dim gp As GenericPrincipal ' Hard-code the roles, for this ' simple example. Select Case LoginID Case "King", "Buchanan" astrRoles(0) = "Managers" Case Else astrRoles(0) = "Users" End Select ' Create Identity gid = New GenericIdentity(LoginID) ' Create Principal gp = New GenericPrincipal(gid, astrRoles) ' Store in a Session variable for later use. Session("Principal") = gp End Sub
The SetIDAndPrincipal procedure sets up the role information for authorization use, later in your application. For demonstration purposes, this procedure adds the Managers role to two employees (King and Buchanan) and the Users role to the rest. (Of course, your own applications could assign roles in other ways, including using Windows' own roles.) Along the way, this procedure takes these actions:
It declares necessary variables. The code will need an array of strings containing role assignments. Because this example uses only a single role, the array can be preassigned to contain only a single element. In addition, the code needs GenericIdentity and GenericPrincipal objects, and it creates those variables here:
Dim astrRoles(0) As String Dim gid As GenericIdentity Dim gp As GenericPrincipal
It hard-codes the roles for all the employees, making simple assumptions about the appropriate role for each, based on the login ID value:
Select Case LoginID Case "King", "Buchanan" astrRoles(0) = "Managers" Case Else astrRoles(0) = "Users" End Select
It creates the new GenericIdentity object, using the login ID. The code then creates the GenericPrincipal object using the new GenericIdentity object and the array of roles filled in previously:
' Create Identity gid = New GenericIdentity(LoginID) ' Create Principal gp = New GenericPrincipal(gid, astrRoles)
Finally, because the rest of your application will need to be able to retrieve role information, the code stores the GenericPrincipal object into a Session variable:
' Store in a Session variable for later use. Session("Principal") = gp
You'll need to add a call to the new SetIDAndPrincipal procedure. To do that, follow these steps:
Make sure the code-behind file for Login.aspx is still loaded in the code editor window. (It should still be loaded if you haven't closed it after executing the previous steps.)
Modify the btnLogin_Click procedure once again, adding the call to SetIDAndPrincipal, as shown in Listing 24.4.
Listing 24.4 Add a Call to SetIDAndPrincipal
Private Sub btnLogin_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnLogin.Click Dim strID As String Dim strPwd As String strID = txtLogin.Text strPwd = txtPassword.Text If LoginValid(strID, strPwd) Then If Session("LoginID").ToString = String.Empty Then Session("LoginID") = strID Session("Password") = strPwd SetIDAndPrincipal(strID) FormsAuthentication.RedirectFromLoginPage( _ strID, False) End If Else Server.Transfer("Main.aspx") End If End Sub
Finally, you'll need to add support to the main page so that you can hide and show links on the page based on the current user's role. That code is in the NWLeftNav.ascx user control. Follow these steps to modify the navigation user control to hide links as necessary:
In the Solution Explorer window, right-click NWLeftNav.ascx and select View Code from the context menu.
Scroll to the top of the file and add the following Imports statement:
Modify the Page_Load procedure so that it looks like this:
Private Sub Page_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Dim blnShow As Boolean Dim blnIsManager As Boolean Dim gp As GenericPrincipal blnShow = _ (Session("LoginID").ToString <> String.Empty) gp = CType(Session("Principal"), GenericPrincipal) blnIsManager = (gp.IsInRole("Managers")) lnkLogOut.Visible = blnShow hypEmpMaint.Visible = blnShow And blnIsManager hypPwdChange.Visible = blnShow And blnIsManager End Sub
The new code you've added to this procedure retrieves the Session variable containing the GenericPrincipal object:
gp = CType(Session("Principal"), GenericPrincipal)
The code uses the IsInRole method of the GenericPrincipal object and determines whether the logged-in user is acting in the Managers role:
blnIsManager = (gp.IsInRole("Managers"))
Finally, the code determines whether to show the hypEmpMaint and hypPwdChange links, based on the role of the user:
hypEmpMaint.Visible = blnShow And blnIsManager hypPwdChange.Visible = blnShow And blnIsManager
(In this code, the blnShow and blnManager values would have to be True in order to have the links appear on the page.)
To test out the role-based authentication, follow these steps:
Press F5 to run the project.
Log in as Buchanan, using 3453 as the password. You'll be redirected to the main page. Verify that you see the maintenance links, because you're currently logged in using the Managers role.
Click the Log Out link, and you'll be redirected back to the login page.
Log in again as Davolio, using 5467 as the password. On the main page, verify that you don't see the maintenance links, because you're now logged in using the Users role.