Membership





Membership

After Microsoft released ASP.NET 1.0 the team members immediately started looking for areas where they could simplify. One of the areas that came up was the management of user credentials, personalization, and user roles. All of these were problems that could be solved in ASP.NET 1.1, but they wanted to make it better . . . and easier!

The Membership feature of ASP.NET does just that—makes it better and easier. Membership provides secure credential storage with simple, easy-to-use APIs. Rather than requiring you to repeatedly develop infrastructure features for authenticating users, it is now part of the platform.

Forms Authentication and Membership complement one another. However, they can also act independently, that is, you don't have to use them together. The code sample in Listing 6.2 demonstrates how Membership is used with Forms Authentication.

Using the Membership API
<script runat="server">

  Public Sub Login_Click(sender As Object, e As EventArgs e)

    ' Is the user valid?
    '
    If (Membership.ValidateUser (Username.Text, Password.Text)) Then

      FormsAuthentication.RedirectFromLoginPage (Username.Text, false)

    Else

      Status.Text = "Invalid Credentials: Please try again"

    End If

  End Sub

</script>

<html>
    <body style="FONT-FAMILY: Verdana">

    <H1>Enter your username/password</H1>

    <form id="Form1" runat="server">
      Username: <asp:textbox id="Username" runat="server" />
      <br>
      Password: <asp:textbox id="Password" runat="server" />
      <p>
      <asp:button id="Button1"
                  text="Check if Member is Valid"
                  onclick="Login_Click" runat="server"/>
    </form>

    <font color="red" size="6">
      <asp:label id="Status" runat="server"/>
    </font>

    </body>
</html>

As you can see, our custom code to validate the credentials is now replaced with a single call to the static Membership.ValidateUser() method. The code is also much cleaner and more readable as a result—and much more concise!

The Membership class contains only static methods. You don't have to create an instance of the class to use its functionality; for example, you don't have to new the Membership class to use it. Behind the scenes the Membership class is forwarding the calls through a configured provider. The provider in turn knows which data source to contact and how to verify the credentials (see Figure).

2. The provider model

graphics/06fig02.gif

Providers are a new "design pattern" introduced with ASP.NET 2.0. Providers are pluggable data abstraction layers used within ASP.NET. All ASP.NET 2.0 features that rely on data storage expose a provider layer. The provider layer allows you to take complete control over how and where data is stored.[1]

[1] For more details on providers, see Chapter 7.

Membership Providers

The beauty of the provider model is the abstraction that it affords the developer. Rather than being pigeonholed into a particular data model, the provider pattern allows the developer to determine how and where the actual data storage takes place.

ASP.NET 2.0 will ship with several providers for Membership (not a complete list):

  • Access

  • SQL Server

  • Active Directory[2]

    [2] Not available with the alpha release of ASP.NET 2.0.

You can also author your own provider and plug it in. The provider design pattern allows for one common API that developers can familiarize themselves with, such as Membership, but under the covers you still have control over what exactly is happening. For example, if you have all of your customer information stored in an AS/400, you could write a provider for Membership. Users would call the familiar Membership APIs, but the work would actually be handled by the configured AS/400 provider.

The goal of Membership is to simplify managing and storing user credentials while still allowing you to control your data, but it does much more. Let's dig deeper.

Setting Up Membership

Setting up Membership is easy: It just works. By default all the providers that ship with ASP.NET 2.0 use a Microsoft Access provider and will use the default AspNetDB.mdb file created in the \data\directory of your application.[3]

[3] Access is the configured default since it works without requiring the user to perform any further setup. SQL Server is the recommended provider for large applications.

If the \data\directory of your application does not exist, ASP.NET will attempt to create it. If ASP.NET is unable to create the \data\directory or the AspNetDB.mdb file due to security policy on the machine, an exception is thrown detailing what needs to be done.

Before we can begin using Membership for its most common task— validating user credentials—we need to have users to validate!

Creating a New User

The Membership API exposes two methods for creating new users:

CreateUser(username As String, password As String)

CreateUser(username As String, password As String,
           email As String)

These two APIs are somewhat self-explanatory. We call them to create a user with a username and password—optionally also providing the e-mail address. Both of these methods return a MembershipUser instance, which we'll look at later in this chapter.

Which of these two methods you use is determined by the Membership configuration settings. We can examine the settings in machine.config for the defaults (see Listing 6.3).[4]

[4] You can also define these settings or change the defaults in the web.config file of your application.

Configuration Elements for the Membership Provider

Attribute

Default Value

Description

connectionStringName[a]

AccessServices

Names the key within the <connectionStrings /> configuration section where the connection string is stored.

enablePasswordRetrieval

False

Controls whether or not the password can be retrieved through the Membership APIs. When set to False (the default), the password cannot be retrieved from the database.

EnablePasswordReset

True

Allows the password to be reset. For example, although the password may not be retrieved, the APIs will allow for a new random password to be created for the user.

requiresQuestionAndAnswer

False

Allows the use of a question and answer to retrieve the user's password. Only valid when the passwordFormat setting is not Hashed and enablePasswordRetrieval is True.

applicationName

/

Indicates the application to which the Membership data store belongs. Multiple applications can share the same Membership data store by specifying the same applicationName value.

RequiresUniqueEmail

False

Requires that a given e-mail address can be used only once. This attribute can be used to prevent users from creating multiple accounts. Note that the uniqueness is constrained to the applicationName the user is created within.

passwordFormat

Hashed

Controls how the password is stored in the data store. Hashed is the most secure but does not allow password retrieval. Additional valid values include Encrypted and Clear.

[a] All connection strings used by ASP.NET features are stored in the <connectionStrings /> configuration section. As of this writing, this feature is not yet complete. Eventually data stored in <connectionStrings /> will be encrypted.

Membership Configuration
<configuration>
  <system.web>

    <membership defaultProvider="AspNetAccessProvider"
                userIsOnlineTimeWindow="15">
      <providers>

        <add
          name="AspNetAccessProvider"
          type="System.Web.Security.AccessMembershipProvider,
                System.Web,
                Version=1.1.3300.0,
                Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
          connectionStringName="AccessServices"
          enablePasswordRetrieval="false"
          enablePasswordReset="true"
          requiresQuestionAndAnswer="false"
          appName="/"
          requiresUniqueEmail="false"
          passwordFormat="Hashed" />

      </providers>
    </membership>

  </system.web>
</configuration>

Figure shows an explanation of the various configuration settings.

Knowing what the defaults are, we can write a simple page for creating new users (see Listing 6.4).

Creating Users with the Membership API
<%@ Page Language="VB" %>

<script runat="server">

  Public Sub CreateUser_Click (sender As Object, e As EventArgs)

   Try

     ' Attempt to create the user
     Membership.CreateUser(Username.Text, Password.Text)

     Status.Text = "Created new user: " & Username.Text

   Catch ex As MembershipCreateUserException

     ' Display the status if an exception occurred
     Status.Text = ex.ToString()

   End Try


  End Sub

</script>
<html>
  <head>
  </head>

  <body style="FONT-FAMILY: Verdana">

    <H1>Create a new user</H1>

    <hr />

    <form runat="server">
    Desired username: <asp:TextBox id="Username" runat="server"/>
    <br>
    Password: <asp:TextBox id="Password" runat="server" />
    <p>
    <asp:button Text="Create Member"
                OnClick="CreateUser_Click" runat="server"/>
    </form>

    <font color="red" size="6">
    <asp:Label id="Status" runat="server" />
    </font>

  </body>
</html>

The code in Listing 6.4 calls the Membership.CreateUser() method, which accepts a username and a password.[5] If there is a problem creating the user, a MembershipCreateUserException is thrown. If there are no problems, the new user is created.

[5] You may already wonder what we do with additional user data, such as first names. Membership is not used to store this type of data. Instead, the new Personalization feature is used to store user data—Membership is used only for storing user credentials used in authentication. Personalization is covered in Chapter 7.

Once we've created some users, we can test the Membership.ValidateUser() method.

Validating User Credentials

As stated earlier, the primary purpose for Membership is to validate credentials. This is accomplished through the static ValidateUser() method:

ValidateUser(username As String,
             password As String) As Boolean

We can use this method, as seen earlier, along with Forms Authentication to validate user credentials. Here is a partial code example:

If (Membership.ValidateUser (Username.Text, Password.Text)) Then

    FormsAuthentication.RedirectFromLoginPage (Username.Text, False)

  Else

    Status.Text = "Invalid Credentials: Please try again"

  End If

Apart from ValidateUser(), most of the remaining Membership APIs are used for retrieving a user or users.

Retrieving a User

There are a few ways you can retrieve users that have already been created:

GetUser() As MembershipUser

GetUser(userIsOnline As Boolean) As MembershipUser

GetUser(username As String) As MembershipUser

GetUser(username As String,
        userIsOnline As Boolean) As MembershipUser

The first two methods that don't have a username parameter will attempt to return the currently logged on user. The parameter userIsOnline, when set to True, will update a timestamp in the data store indicating the date/time the user was last requested. This timestamp can then be used to calculate the total number of users on-line.[6] The remaining methods will perform similar operations but on a specified user.

[6] This functionality is similar to that used on the ASP.NET Forums (http://www.asp.net/Forums/). All users have a timestamp that can be updated. The number of users on-line is calculated by finding all users whose timestamps fall within a calculated window of time. This time window is configured in the <membership> configuration setting userIsOnlineTimeWindow.

Figure shows an example of getting the MembershipUser class for the currently logged on user.

3. Getting a user

graphics/06fig03.gif

Listing 6.5 provides the code used for this page.

Fetching the Logged on User
<%@ Page Language="VB" %>

<script runat="server">

  Public Sub Page_Load()
    Dim user As MembershipUser

    ' Get the currently logged on user and
    ' update the user's on-line timestamp
    user = Membership.GetUser(True)

    UserName.Text = user.Username

  End Sub

</script>

<html>

  <body style="FONT-FAMILY: Verdana">

  <H1>Get User</H1>

  <hr />

  <form runat="server">
    The currently logged on user is:
    <asp:literal id="UserName" runat="server" />
  </form>

  </body>
</html>

If we want to find a user but don't have the username (e.g., the user forgot his or her username), we can use the GetUserNameByEmail() method:

GetUserNameByEmail(email As String) As String

Once we have the username, we can then look up the user with one of the GetUser() methods listed earlier.

We can additionally get multiple users with the following method:

Membership.GetAllUsers() As MembershipUserCollection

IMPORTANT

At the time of this writing, another Membership method, GetUsers(), is not yet implemented. The GetUsers() method will be designed to allow for multiple users to be returned by using specific criteria, including page ranges. Thus if there are millions of users in the data store, only the requested range will be returned. The GetAllUsers() method will likely be removed.

Membership.GetAllUsers() simply returns a MembershipUser Collection, which we can use to enumerate users or bind to a server control, such as a Repeater or DataGrid (see Figure).

4. Getting all users

graphics/06fig04.gif

Listing 6.6 shows the code.

Displaying All Users
<%@ Page Language="VB" %>

<script runat="server">

  Public Sub Page_Load()

    Users.DataSource = Membership.GetAllUsers()
    Users.DataBind()

  End Sub

</script>

<html>
  <head>
  </head>

  <body style="FONT-FAMILY: Verdana">

    <H1>Users in Membership Database</H1>

    <hr />

    <asp:repeater id="Users" runat="server">
      <headertemplate>
        <table border="1">
          <tr>
            <td bgcolor="black" style="color:white">
                Username
            </td>

            <td bgcolor="black" style="color:white">
                Email
            </td>

            <td bgcolor="black" style="color:white">
                Is Online
            </td>

            <td bgcolor="black" style="color:white">
                Is Approved
            </td>

            <td bgcolor="black" style="color:white">
                Date Last Active
            </td>

            <td bgcolor="black" style="color:white">
                Date Created
            </td>

            <td bgcolor="black" style="color:white">
                Date Password Changed
            </td>

            <td bgcolor="black" style="color:white">
                Password Question
            </td>
          </tr>
      </headertemplate>

      <itemtemplate>
        <tr>
          <td>
            <%# Eval("Username") %>
          </td>

          <td>
            <%# Eval("Email") %>
          </td>

          <td>
            <%# Eval("IsOnline") %>
          </td>

          <td>
            <%# Eval("IsApproved") %>
          </td>

          <td>
            <%# Eval("LastLoginDate") %>
          </td>

        <td>
          <%# Eval("LastActivityDate") %>
        </td>

        <td>
          <%# Eval("CreationDate") %>
        </td>

        <td>
          <%# Eval("LastPasswordChangedDate") %>
        </td>

        <td>
          <%# Eval("PasswordQuestion") %>
        </td>
      </tr>
    </itemtemplate>

    <footertemplate>
      </table>
    </footertemplate>
  </asp:repeater>


  </body>
</html>

Now that we've looked at how to create users and retrieve named users, let's look at the MembershipUser class, which allows us to set and retrieve extended properties for each user.

The MembershipUser Class

The MembershipUser class represents a user stored in the Membership system. It provides the following methods for performing user-specific operations, such as retrieving or resetting a user's password.

GetPassword() As String

GetPassword(answer As String) As String

ChangePassword(oldPassword As String,
               newPassword As String) As Boolean

ChangePasswordQuestionAndAnswer(password As String,
                                question As String,
                                answer As String) As Boolean

ResetPassword() As String

ResetPassword(answer As String) As String

Note that if a question and answer are being used, the overloaded GetPassword(answer As String) requires the case-insensitive question answer.

The ChangePassword() method allows changes to the user's password, and the ChangePasswordQuestionAndAnswer() method allows changes to the user's password question and answer. The code in Listing 6.7 allows the currently logged on user to change his or her password question and answer.[7]

[7] When the <membership /> configuration's requiresQuestionAndAnswer is set to true, the GetPassword(answer As String) and ResetPassword(answer As String) must be used to either retrieve or reset the user's password. (The answer value is the answer to the user's question.)

Changing a Password
<%@ Page Language="VB" %>

<script runat="server">

  Public Sub Page_Load()

    If Not Page.IsPostBack Then
      DisplayCurrentQuestion()
    End If

  End Sub

  Public Sub SetQandA_Click(sender As Object, e As EventArgs)

    Dim u As MembershipUser = Membership.GetUser()

    u.ChangePasswordQuestionAndAnswer(CurrentPassword.Text, _
                                      Question.Text, _
                                      Answer.Text)

    Membership.UpdateUser(u)

    DisplayCurrentQuestion()
  End Sub

  Public Sub DisplayCurrentQuestion()

    Status.Text = Membership.GetUser().PasswordQuestion

  End Sub

</script>

<html>
  <body style="FONT-FAMILY: Verdana">

  <H1>Set Question Answer</H1>

  <hr />

  <form id="Form1" runat="server">
    Current Password: <asp:textbox id="CurrentPassword"
                                   runat="server" />
    <p></p>
    Question: <asp:textbox id="Question" runat="server" />
    <p></p>
    Answer:  <asp:textbox id="Answer" runat="server" />
    <p></p>
    <asp:button id="Button1" text="Set Question/Answer"
                onclick="SetQandA_Click" runat="server"/>
  </form>

  <font size="6"> Your new password question is:
  <asp:label id="Status" runat="server"/>
  </font>

</html>

The ResetPassword() methods are similar to the GetPassword() methods. However, rather than retrieving the user's password, they reset and then return a random password for the user.

Keep in mind that the ability to retrieve, change, or reset the user's password is determined by the settings within the configuration.

In addition to password management, the MembershipUser class has some useful properties that provide us some details about how and when the user last logged in, last changed passwords, and so on (see Figure).

Updating a User's Properties

When changes are made to the user, for example, updating the user's e-mail address, we need to use the Membership.UpdateUser(user As Membership User) method to save the values.[8] For example, in Listing 6.7 above, the SetQandA_Click event (repeated for convenience below) shows an example of Membership.UpdateUser():

[8] The goal of this design is to allow multiple values to be changed without requiring multiple round-trips to the data store. By using the UpdateUser() method, all updates are batched together.

Public Sub SetQandA_Click(sender As Object, e As EventArgs)
    Dim u As MembershipUser = Membership.GetUser()

    u.ChangePasswordQuestionAndAnswer(CurrentPassword.Text,
                                      Question.Text,
                                      Answer.Text)

    Membership.UpdateUser(u)

    DisplayCurrentQuestion()
End Sub

MembershipUser Properties

Property

Description

LastLoginDate

Sets or returns a timestamp for the last time ValidateUser() was called for the current MembershipUser.

CreationDate

Sets or returns a timestamp value set when the user was first created.

LastActivityDate

Sets or returns a timestamp value set when the user authenticates or is retrieved using the overloaded GetUser() method that accepts a userIsOnline parameter.

LastPasswordChangedDate

Sets or returns a timestamp value set when the user last changed his or her password.

Email

Sets or returns the e-mail address, if set, of the user.

IsApproved

Sets or returns a value that indicates whether or not the user is approved. Users whose IsApproved property is set to false cannot log in, even when the specified credentials are valid.

PasswordQuestion

Returns the question used in question/answer retrieval.

Provider

Returns an instance (of type IMembershipProvider) of the current provider used to manipulate the data store.

Username

Returns the username of the current user.

So far we've learned how to create and update users, but what about removing users from the Membership system?

Deleting a User

Deleting a user from Membership is easy. Membership supports a single method for removing users:

DeleteUser(username As String) As Boolean

We simply need to name the user we wish to delete. If the operation is successful, the method returns True. If the delete operation fails, for example, if the user doesn't exist, False is returned.

Listing 6.8 shows a code example that allows us to specify a user to be removed from the Membership system.

Deleting a User
<%@ Page Language="VB" %>

<script runat="server">

  Public Sub DeleteUser_Click(sender As Object, e As EventArgs)

    If (Membership.DeleteUser(Username.Text)) Then
      Status.Text = Username.Text & " deleted"
    Else
      Status.Text = Username.Text & " not deleted"
    End If

  End Sub

</script>
<html>
  <head>
  </head>

  <body style="FONT-FAMILY: Verdana">


    <H1>Delete a user</H1>

    <hr />

    <form runat="server">
      Username to delete: <asp:TextBox id="Username"
                                       runat="server"/>
      <p>
      <asp:button Text="Delete User"
                  OnClick="DeleteUser_Click" runat="server"/>
    </form>

    <font color="red" size="6">
    <asp:label id="Status" runat="server" />
    </font>

  </body>
</html>

Figure shows how this page looks.

5. Deleting a user

graphics/06fig05.gif

While the Membership APIs definitely simplify day-to-day tasks, there is also an alternative to using programmatic APIs: security server controls. In many cases we can use these server controls and never have to write code that uses the Membership APIs!


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows