Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

 

patterns & practices Developer Center

How To: Implement IPrincipal in ASP.NET 1.1

J.D. Meier, Alex Mackman, Michael Dunner, and Srinath Vasireddy
Microsoft Corporation

Published: November 2002

Last Revised: January 2006

Applies to:

  • .NET Framework 1.1

See the "patterns & practices Security Guidance for Applications Index" for links to additional security resources.

See the Landing Page for a starting point and complete overview of Building Secure ASP.NET Applications.

Summary: This How To shows you how to create a custom principal object that provides extended role-based functionality that can be used for .NET authorization. (12 printed pages)

Contents

Summary of Steps Step 1. Create a Simple Web Application Step 2. Configure the Web Application for Forms Authentication Step 3. Generate an Authentication Ticket for Authenticated Users Step 4. Create a Class that Implements and Extends IPrincipal Step 5. Create the CustomPrincipal Object Step 6. Test the Application
Additional Resources

The .NET Framework provides the WindowsPrincipal and GenericPrincipal classes, which provide basic role-checking functionality for Windows and non-Windows authentication mechanisms respectively. Both classes implement the IPrincipal interface. To be used for authorization, ASP.NET requires that these objects are stored in HttpContext.User. For Windows-based applications, they must be stored in Thread.CurrentPrincipal.

The functionality offered by these classes is sufficient for most application scenarios. Applications can explicitly call the IPrincipal. IsInRole method to perform programmatic role checks. The Demand method of the PrincipalPermission class, when used to demand that a caller belong to a particular role (either declaratively or imperatively) also results in a call to IPrincipal.IsInRole.

In some circumstances, you might need to develop your own principal implementations by creating a class that implements the IPrincipal interface. Any class that implements IPrincipal can be used for .NET authorization.

Reasons for implementing your own IPrincipal class include:

  • You want extended role checking functionality. You might want methods that allow you to check whether a particular user is a member of multiple roles. For example:

    CustomPrincipal.IsInAllRoles( "Role1", "Role2", "Role3" )
    CustomPrincipal.IsInAnyRole( "Role1", "Role2", "Role3" )
    
  • You want to implement an extra method or property that returns a list of roles in an array. For example:

    string[] roles = CustomPrincipal.Roles;
    
  • You want your application to enforce role hierarchy logic. For example, a Senior Manager may be considered higher up in the hierarchy than a Manager. This could be tested using methods like the following.

    CustomPrincipal.IsInHigherRole("Manager");
    CustomPrincipal.IsInLowerRole("Manager");
    
  • You want to implement lazy initialization of the role lists. For example, you could dynamically load the role list only when a role check is requested.

This How To describes how to implement a custom IPrincipal class and use it for role-based authorization in an ASP.NET application that uses Forms authentication.

Summary of Steps

This How To includes the following steps:

  • Step 1. Create a Simple Web Application
  • Step 2. Configure the Web Application for Forms Authentication
  • Step 3. Generate an Authentication Ticket for Authenticated Users
  • Step 4. Create a Class that Implements and Extends IPrincipal
  • Step 5. Create the CustomPrincipal Object
  • Step 6. Test the Application

Step 1. Create a Simple Web Application

This procedure creates a new ASP.NET Web application. The application will contain two pages, a default page that only authenticated users are allowed to access and a logon page used to collect user credentials.

To create a simple Web application

  1. Start Visual Studio .NET and create a new C# ASP.NET Web Application called CustomPrincipalApp.

  2. Rename WebForm1.aspx to Logon.aspx.

  3. Add the controls listed in Table 1 to Logon.aspx to create a logon form.

    Table 1: Logon.aspx controls

    Control Type Text ID
    Label User Name: -
    Label Password -
    Text Box - txtUserName
    Text Box - txtPassword
    Button Logon btnLogon
  4. Set the TextMode property of the password Text Box control to Password.

  5. In Solution Explorer, right-click CustomPrincipalApp, point to Add, and then click Add Web Form.

  6. Enter default.aspx as the new form's name, and then click Open.

Step 2. Configure the Web Application for Forms Authentication

To edit the application's Web.config file to configure the application for Forms authentication

  1. Use Solution Explorer to open Web.config.

  2. Locate the <authentication> element and change the mode attribute to Forms.

  3. Add the following <forms> element as a child of the <authentication> element and set the loginUrl, name, timeout, and path attributes as follows:

    <authentication mode="Forms">
      <forms loginUrl="logon.aspx" name="AuthCookie" timeout="60" 
        path="/">
      </forms>
    </authentication>
    
  4. Add the following <authorization> element beneath the <authentication> element. This allows only authenticated users to access the application. The previously established loginUrl attribute of the <authentication> element redirects unauthenticated requests to the Logon.aspx page.

    <authorization> 
      <deny users="?" />
      <allow users="*" />
    </authorization>
    

Step 3. Generate an Authentication Ticket for Authenticated Users

This procedure writes code to generate an authentication ticket for authenticated users. The authentication ticket is a type of cookie used by the ASP.NET FormsAuthenticationModule.

The authentication code typically involves looking up the supplied user name and password against either a custom database or against Microsoft Active Directory® directory service.

For information about performing these lookups, see the following How To articles in the Reference section of this guide:

To generate an authentication ticket for authenticated users

  1. Open the Logon.aspx.cs file and the following using statement to the top of the file beneath the existing using statements.

    using System.Web.Security;
    
  2. Add the following private helper method to the WebForm1 class called IsAuthenticated, which is used to validate user names and passwords to authenticate users. This code assumes that all user name and password combinations are valid.

    private bool IsAuthenticated( string username, string password )
    {
      // Lookup code omitted for clarity
      // This code would typically validate the user name and password
      // combination against a SQL database or Active Directory
      // Simulate an authenticated user
      return true;
    }
    
  3. Add the following private helper method called GetRoles, which is used to obtain the set of roles that the user belongs to.

    private string GetRoles( string username)
    {
      // Lookup code omitted for clarity
      // This code would typically look up the role list from a database 
      // table. If the user was being authenticated against Active 
      // Directory, the Security groups and/or distribution lists that 
      // the user belongs to may be used instead
    
      // This GetRoles method returns a pipe delimited string containing 
      // roles rather than returning an array, because the string format 
      // is convenient for storing in the authentication ticket / 
      // cookie, as user data
      return "Senior Manager|Manager|Employee";
    }
    
  4. Display the Logon.aspx form in Designer mode, and then double-click the Logon button to create a click event handler.

  5. Add a call to the IsAuthenticated method, supplying the user name and password captured through the logon form. Assign the return value to a variable of type bool, which indicates whether or not the user is authenticated.

    bool isAuthenticated = IsAuthenticated( txtUserName.Text,
                                            txtPassword.Text );
    
  6. If the user is authenticated, add a call to the GetRoles method to obtain the user's role list.

    if (isAuthenticated == true )
    {
      string roles = GetRoles( txtUserName.Text);
    
  7. Create a new forms authentication ticket that contains the user name, an expiration time, and the list of roles that the user belongs to. Note that the user data property of the authentication ticket is used to store the user's role list. Also note that the following code creates a non-persistent ticket, although whether or not the ticket / cookie is persistent is dependent upon your application scenario.

      // Create the authentication ticket
      FormsAuthenticationTicket authTicket = new 
           FormsAuthenticationTicket(1,                          // 
             version
                                     txtUserName.Text,           // user 
                                       name
                                     DateTime.Now,               // 
                                       creation
                                     DateTime.Now.AddMinutes(60),// 
                                       Expiration
                                     false,                      // 
                                       Persistent
                                     roles );                    // User 
                                       data
    
  8. Add code to create an encrypted string representation of the ticket and store it as data within an HttpCookie object.

      // Now encrypt the ticket.
      string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
      // Create a cookie and add the encrypted ticket to the 
      // cookie as data.
      HttpCookie authCookie =
                   new HttpCookie(FormsAuthentication.FormsCookieName,
                                  encryptedTicket);
    
  9. Add the cookie to the cookies collection returned to the user's browser.

      // Add the cookie to the outgoing cookies collection. 
      Response.Cookies.Add(authCookie); 
    
  10. Redirect the user to the originally requested page.

      // Redirect the user to the originally requested page
      Response.Redirect( FormsAuthentication.GetRedirectUrl(
                                                    txtUserName.Text,
                                                    false ));
    }
    

Step 4. Create a Class that Implements and Extends IPrincipal

This procedure creates a class that implements the IPrincipal interface. It also adds additional methods and properties to the class to provide additional role-based authorization functionality.

To create a class that implements and extends IPrincipal

  1. Add a new class called CustomPrincipal to the current project.

  2. Add the following using statement to the top of CustomPrincipal.cs.

    using System.Security.Principal;
    
  3. Derive the CustomPrincipal class from the IPrincipal interface.

    public class CustomPrincipal : IPrincipal
    
  4. Add the following private member variables to the class to maintain the IIdentity object associated with the current principal and the principal's role list.

    private IIdentity _identity;
    private string [] _roles;
    
  5. Modify the class' default constructor to accept an IIdentity object and array of roles. Use the supplied values to initialize the private member variables as shown below.

    public CustomPrincipal(IIdentity identity, string [] roles)
    {
      _identity = identity;
      _roles = new string[roles.Length];
      roles.CopyTo(_roles, 0);
      Array.Sort(_roles);
    }
    
  6. Implement the IsInRole method and Identity property defined by the IPrincipal interface as shown below.

    // IPrincipal Implementation
    public bool IsInRole(string role)
    {
      return Array.BinarySearch( _roles, role ) >=0 ? true : false;
    }
    public IIdentity Identity
    {
      get
      {
        return _identity;
      }
    }
    
  7. Add the following two public methods, which provide extended role-based checking functionality.

    // Checks whether a principal is in all of the specified set of 
      roles
    public bool IsInAllRoles( params string [] roles )
    {
      foreach (string searchrole in roles )
      {
        if (Array.BinarySearch(_roles, searchrole) < 0 )
          return false;
      }
      return true;
    }
    // Checks whether a principal is in any of the specified set of 
      roles
    public bool IsInAnyRoles( params string [] roles )
    {
      foreach (string searchrole in roles )
      {
        if (Array.BinarySearch(_roles, searchrole ) > 0 )
          return true;
      }
      return false;
    }
    

Step 5. Create the CustomPrincipal Object

This procedure implements an application authentication event handler and constructs a CustomPrincipal object to represent the authenticated user based on information contained within the authentication ticket.

To construct the CustomPrincipal object

  1. From Solution Explorer, open global.asax.

  2. Switch to code view, and then add the following using statements to the top of the file.

    using System.Web.Security;
    using System.Security.Principal;
    
  3. Locate the Application_AuthenticateRequest event handler and add the following code to obtain the forms authentication cookie from the cookie collection passed with the request.

    // Extract the forms authentication cookie
    string cookieName = FormsAuthentication.FormsCookieName;
    HttpCookie authCookie = Context.Request.Cookies[cookieName];
    
    if(null == authCookie)
    {
      // There is no authentication cookie.
      return;
    } 
    
  4. Add the following code to extract and decrypt the authentication ticket from the forms authentication cookie.

    FormsAuthenticationTicket authTicket = null;
    try
    {
      authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    }
    catch(Exception ex)
    {
      // Log exception details (omitted for simplicity)
      return;
    }
    
    if (null == authTicket)
    {
      // Cookie failed to decrypt.
      return; 
    } 
    
  5. Add the following code to parse out the pipe separate list of role names attached to the ticket when the user was originally authenticated.

    // When the ticket was created, the UserData property was assigned a
    // pipe delimited string of role names.
    string[] roles = authTicket.UserData.Split('|');
    
  6. Add the following code to create a FormsIdentity object with the user name obtained from the ticket name and a CustomPrincipal object that contains this identity together with the user's role list.

    // Create an Identity object
    FormsIdentity id = new FormsIdentity( authTicket ); 
    
    // This principal will flow throughout the request.
    CustomPrincipal principal = new CustomPrincipal(id, roles);
    // Attach the new principal object to the current HttpContext object
    Context.User = principal;
    

Step 6. Test the Application

This procedure adds code to the default.aspx page to display information from the CustomPrincipal object attached to the current HttpContext object, to confirm that the object has been correctly constructed and assigned to the current Web request. It also tests the role-based functionality supported by the new class.

To test the application

  1. In Solution Explorer, double-click default.aspx.

  2. Double-click the default.aspx Web form to display the page load event handler.

  3. Scroll to the top of the file and add the following using statement beneath the existing using statements.

    using System.Security.Principal;
    
  4. Return to the page load event handler and add the following code to display the identity name attached to the CustomPrincipal associated with the current Web request.

    CustomPrincipal cp = HttpContext.Current.User as CustomPrincipal;
    Response.Write( "Authenticated Identity is: " +
                    cp.Identity.Name );
    Response.Write( "<p>" );
    
  5. Add the following code to test role membership for the current authenticated identity, using the standard IsInRole method and the additional IsInAnyRoles and IsInAllRoles methods supported by the CustomPrincipal class.

    if ( cp.IsInRole("Senior Manager") )
    {
      Response.Write( cp.Identity.Name + " is in the " + "Senior Manager 
        Role" );
      Response.Write( "<p>" );            
    }
    
      if ( cp.IsInAnyRoles("Senior Manager", "Manager", "Employee", 
        "Sales") )
      {
        Response.Write( cp.Identity.Name + " is in one of the specified 
          roles");
        Response.Write( "<p>" );
      }
      if ( cp.IsInAllRoles("Senior Manager", "Manager", "Employee", 
        "Sales") )
      {
        Response.Write( cp.Identity.Name + " is in ALL of the specified 
          roles" );
        Response.Write( "<p>" );
      }
      else
      {
        Response.Write( cp.Identity.Name + 
                        " is not in ALL of the specified roles" );
        Response.Write("<p>");
      }
    
      if ( cp.IsInRole("Sales") )
        Response.Write( "User is in Sales role<p>" );
      else
        Response.Write( "User is not in Sales role<p>" );
    
  6. In Solution Explorer, right-click default.aspx, and then click Set As Start Page.

  7. On the Build menu, click Build Solution.

  8. Press CTRL+F5 to run the application. Because default.aspx is configured as the start up page, this is the initially requested page.

  9. When you are redirected to the logon page (because you do not initially have an authentication ticket), enter a user name and password (any will do), and then click Logon.

  10. Confirm that you are redirected to default.aspx and that the user identity and the correct role details are displayed. The user is a member of the Senior Manager, Manager and Employee roles, but not a member of the Sales role.

Additional Resources

For more information about Forms based authentication, see the following How Tos in the Reference section of this guide:

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

© Microsoft Corporation. All rights reserved.