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: Use Forms Authentication with Active Directory 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:

  • ASP.NET 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 article shows you how to implement Forms authentication against an Active Directory credential store. (11 printed pages)

Contents

Summary of Steps Step 1. Create a Web Application with a Logon Page Step 2. Configure the Web Application for Forms Authentication Step 3. Develop LDAP Authentication Code to Look Up the User in Active Directory Step 4. Develop LDAP Group Retrieval Code to Look Up the User's Group Membership Step 5. Authenticate the User and Create a Forms Authentication Ticket Step 6. Implement an Authentication Request Handler to Construct a GenericPrincipal Object Step 7. Test the Application

ASP.NET Forms authentication allows users to identify themselves by entering credentials (a user name and password) into a Web Form. Upon receipt of these credentials, the Web application can authenticate the user by checking the user name and password combination against a data source.

This How To describes how to authenticate users against the Microsoft Active Directory directory service by using the Lightweight Directory Access Protocol (LDAP). It also describes how to retrieve a list of security groups and distribution lists to which the user belongs, how to store this information in a GenericPrincipal object, and how to store this into the HttpContext.Current.User property that flows with the request through the ASP.NET Web application. This can subsequently be used for .NET role-based authorization.

Summary of Steps

This How To includes the following steps:

  • Step 1. Create a Web Application with a Logon Page
  • Step 2.Configure the Web Application for Forms Authentication
  • Step 3.Develop LDAP Authentication Code to Look Up the User in Active Directory
  • Step 4.Develop LDAP Group Retrieval Code to Look Up the User's Group Membership
  • Step 5.Authenticate the User and Create a Forms Authentication Ticket
  • Step 6.Implement an Authentication Request Handler to Construct a GenericPrincipal Object
  • Step 7.Test the Application

Step 1. Create a Web Application with a Logon Page

This procedure creates a simple C# Web application that contains a logon page that allows a user to enter a user name and password and a default page that displays the identity name and group membership information associated with the current Web request.

To create a Web application with a logon page

  1. Start Microsoft Visual Studio® .NET and create a new C# ASP.NET Web Application named FormsAuthAD.

  2. Use Solution Explorer to rename WebForm1.aspx as Logon.aspx.

  3. Add a new assembly reference to System.DirectoryServices.dll. This provides access to the System.DirectoryServices namespace that contains managed types to help with Active Directory querying and manipulation.

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

    Table 1. Logon.aspx controls

    Control Type Text ID
    Label Domain Name: -
    Label User Name: -
    Label Password -
    Text Box - txtDomainName
    Text Box - txtUserName
    Text Box - txtPassword
    Button Log On btnLogon
    Label   lblError
  5. Set the TextMode property of txtPassword to Password.

  6. In Solution Explorer, right-click FormsAuthAd, point to Add, and then click Add Web Form.

  7. In the Name field, type default.aspx, and then click Open.

  8. In Solution Explorer, right-click default.aspx, and then click Set As Start Page.

  9. Double-click default.aspx to display the page load event handler.

  10. Add the following code to the event handler to display the identity name associated with the current Web request.

    Response.Write( HttpContext.Current.User.Identity.Name );
    

Step 2. Configure the Web Application for Forms Authentication

This procedure edits the application's Web.config file to configure the application for Forms authentication.

To configure the Web 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 shown in the following.

    <authentication mode="Forms">
      <forms loginUrl="logon.aspx" name="adAuthCookie" timeout="60"
        path="/">
      </forms>
    </authentication>
    
  4. Add the following <authorization> element beneath the <authentication> element. This will allow only authenticated users to access the application. The previously establish loginUrl attribute of the <authentication> element will redirect unauthenticated requests to the logon.aspx page.

    <authorization> 
      <deny users="?" />
      <allow users="*" />
    </authorization>
    
  5. Save Web.config.

  6. Start the IIS Microsoft Management Console (MMC) snap-in.

  7. Right-click the application's virtual directory, and then click Properties.

  8. Click the DirectorySecurity tab, and then click the Edit button in the Anonymous access and authentication control group.

  9. Select the Anonymousaccess check box and clear the Allow IIS to control password check box.

  10. Because the default anonymous account IUSR_MACHINE does not have permission to access Active Directory, create a new least privileged account and enter the account details in the AuthenticationMethods dialog box.

  11. Click OK, and then click OK again to close the Properties dialog box.

  12. Return to Visual Studio .NET and add an <identity> element beneath the <authorization> element in Web.config and set the impersonate attribute to true. This causes ASP.NET to impersonate the anonymous account specified earlier.

    <identity impersonate="true" />
    

    As a result of this configuration, all requests to the application will run under the security context of the configured anonymous account. The user will provide credentials through the Web form to authenticate against Active Directory, but the account used to access Active Directory will be the configured anonymous account.

Step 3. Develop LDAP Authentication Code to Look Up the User in Active Directory

This procedure adds a new helper class to the Web application to encapsulate the LDAP code. The class will initially provide an IsAuthenticated method to validate a supplied domain, user name, and password against an Active Directory user object.

To develop LDAP authentication code to look up the user in Active Directory

  1. Add a new C# class file called LdapAuthentication.cs.

  2. Add a reference to the System.DirectoryServices.dll assembly.

  3. Add the following using statements to the top of LdapAuthentication.cs.

    using System.Text;
    using System.Collections;
    using System.DirectoryServices;
    
  4. Rename the existing namespace as FormsAuthAD.

  5. Add two private strings to the LdapAuthentication class; one to hold the LDAP path to Active Directory and the other to hold a filter attribute used for searching Active Directory.

    private string _path;
    private string _filterAttribute;
    
  6. Add a public constructor that can be used to initialize the Active Directory path.

    public LdapAuthentication(string path)
    {
      _path = path;
    }
    
  7. Add the following IsAuthenticated method that accepts a domain name, user name and password as parameters and returns bool to indicate whether or not the user with a matching password exists within Active Directory. The method initially attempts to bind to Active Directory using the supplied credentials. If this is successful, the method uses the DirectorySearcher managed class to search for the specified user object. If located, the _path member is updated to point to the user object and the _filterAttribute member is updated with the common name attribute of the user object.

    public bool IsAuthenticated(string domain, string username, string
      pwd)
    {
      string domainAndUsername = domain + @"\" + username;
      DirectoryEntry entry = new DirectoryEntry( _path, 
                                                 domainAndUsername,
                                                   pwd);
    
      try
      { 
        // Bind to the native AdsObject to force authentication.
        Object obj = entry.NativeObject;
        DirectorySearcher search = new DirectorySearcher(entry);
        search.Filter = "(SAMAccountName=" + username + ")";
        search.PropertiesToLoad.Add("cn");
        SearchResult result = search.FindOne();
        if(null == result)
        {
          return false;
        }
        // Update the new path to the user in the directory
        _path = result.Path;
        _filterAttribute = (String)result.Properties["cn"][0];
      }
      catch (Exception ex)
      {
        throw new Exception("Error authenticating user. " + ex.Message);
      }
      return true;
    }
    

Step 4. Develop LDAP Group Retrieval Code to Look Up the User's Group Membership

This procedure extends the LdapAuthentication class to provide a GetGroups method, which will retrieve the list of groups that the current user is a member of. The GetGroups method will return the group list as a pipe separated string, as in the following.

"Group1|Group2|Group3|"

To develop LDAP group retrieval code to look up the user's group membership

  1. Add the following implementation of the GetGroups method to the LdapAuthentication class.

    public string GetGroups()
    {
      DirectorySearcher search = new DirectorySearcher(_path);
      search.Filter = "(cn=" + _filterAttribute + ")";
      search.PropertiesToLoad.Add("memberOf");
      StringBuilder groupNames = new StringBuilder();
      try
      {
        SearchResult result = search.FindOne();
        int propertyCount = result.Properties["memberOf"].Count;
        String dn;
        int equalsIndex, commaIndex;
    
        for( int propertyCounter = 0; propertyCounter < propertyCount;
             propertyCounter++)
        {
          dn = (String)result.Properties["memberOf"][propertyCounter];
    
          equalsIndex = dn.IndexOf("=", 1);
          commaIndex = dn.IndexOf(",", 1);
          if (-1 == equalsIndex)
          {
            return null;
          }
          groupNames.Append(dn.Substring((equalsIndex + 1), 
                            (commaIndex - equalsIndex) - 1));
          groupNames.Append("|");
        }
      }
      catch(Exception ex)
      {
        throw new Exception("Error obtaining group names. " +
          ex.Message);
      } 
      return groupNames.ToString();
    }
    

Step 5. Authenticate the User and Create a Forms Authentication Ticket

This procedure implements the btnLogon_Click event handler to authenticate users. For authenticated users, you will then create a Forms authentication ticket that contains the user's group list. You will then redirect the user to the original page that they requested (before being redirected to the logon page).

To authenticate the user and create a forms authentication ticket

  1. Return to the Logon.aspx form and double-click the LogOn button to create an empty btnLogon_Click event handler.

  2. At the top of the file add the following using statement beneath the existing using statements. This provides access to the FormsAuthentication methods.

    using System.Web.Security;
    
  3. Add code to create a new instance of the LdapAuthentication class initialized to point to your LDAP Active Directory, as shown in the following code. Remember to change the path to point to your Active Directory server.

    // Path to you LDAP directory server.
    // Contact your network administrator to obtain a valid path.
    string adPath = 
      "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com"; 
    LdapAuthentication adAuth = new LdapAuthentication(adPath);
    
  4. Add the code that follows to perform the following steps:

    1. Authenticate the caller against Active Directory.
    2. Retrieve the list of groups that the user is a member of.
    3. Create a FormsAuthenticationTicket that contains the group list.
    4. Encrypt the ticket.
    5. Create a new cookie that contains the encrypted ticket.
    6. Add the cookie to the list of cookies returned to the user's browser.
    try
    {
      if(true == adAuth.IsAuthenticated(txtDomainName.Text,
                                        txtUserName.Text,
                                        txtPassword.Text))
      {
        // Retrieve the user's groups
        string groups = adAuth.GetGroups();
        // Create the authetication ticket
        FormsAuthenticationTicket authTicket = 
            new FormsAuthenticationTicket(1,  // version
                                          txtUserName.Text,
                                          DateTime.Now, 
                                          DateTime.Now.AddMinutes(60),
                                          false, groups);
        // 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);
        // Add the cookie to the outgoing cookies collection.
        Response.Cookies.Add(authCookie); 
    
        // Redirect the user to the originally requested page
        Response.Redirect(
                  FormsAuthentication.GetRedirectUrl(txtUserName.Text,
                                                     false));
      }
      else
      {
        lblError.Text = 
             "Authentication failed, check username and password.";
      }
    }
    catch(Exception ex)
    {
      lblError.Text = "Error authenticating. " + ex.Message;
    }
    

Step 6. Implement an Authentication Request Handler to Construct a GenericPrincipal Object

This procedure implements the Application_AuthenticateRequest event handler within global.asax and creates a GenericPrincipal object for the currently authenticated user. This will contain the list of groups that the user is a member of, retrieved from the FormsAuthenticationTicket contained in the authentication cookie. Finally, you will associate the GenericPrincipal object with the current HttpContext object that is created for each Web request.

To implement an authentication request handler to construct a GenericPrincipal object

  1. Use Solution Explorer to open global.asax.cs.

  2. 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 cookie that contains the encrypted FormsAuthenticationTicket, 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 FormsAuthenticationTicket from the 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 group 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 group names.
    String[] groups = authTicket.UserData.Split(new char[]{'|'});
    
  6. Add the following code to create a GenericIdentity object with the user name obtained from the ticket name and a GenericPrincipal object that contains this identity together with the user's group list.

    // Create an Identity object
    GenericIdentity id = new GenericIdentity(authTicket.Name,
                                             "LdapAuthentication");
    
    // This principal will flow throughout the request.
    GenericPrincipal principal = new GenericPrincipal(id, groups);
    // Attach the new principal object to the current HttpContext object
    Context.User = principal;
    

Step 7. Test the Application

This procedure uses the Web application to request the default.aspx page. You will be redirected to the logon page for authentication. Upon successful authentication, your browser will be redirected to the originally requested default.aspx page. This will extract and display the list of groups that the authenticated user belongs to from the GenericPrincipal object that has been associated with the current request by the authentication process.

To test the application

  1. On the Build menu, click BuildSolution.

  2. In Solution Explorer, right-click default.aspx, and then click View in Browser.

  3. Enter a valid domain name, user name, and password and then click LogOn.

  4. If you are successfully authenticated, you should be redirected back to default.aspx. The code on this page should display the user name of the authenticated user.

    To see the list of groups the authenticated user is a member of, add the following code at the end of the Application_AuthenticateRequest event handler in the global.aspx.cs file.

    Response.Write("Groups: " + authTicket.UserData + "<br>");
    

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.