We recommend using Visual Studio 2017

Building a Custom Registration and Login Control

 

G. Andrew Duthie
Graymad Enterprises, Inc

January 2004

Applies to:
    Microsoft® ASP.NET

Summary: One anticipated feature in the upcoming version of ASP.NET, code name ASP.NET "Whidbey" (after the code name for the upcoming release of Visual Studio .NET), is the addition of controls to assist in registering and authenticating users. This article shows how you can create a control to add this functionality to ASP.NET 1.1. (21 printed pages)

Download RegLoginControlSample.msi.

Contents

Introduction
Control Overview
Creating the Basic Control
Building the UI
Handling Postbacks
Verifying and Saving Credentials
Configuring Forms Authentication and Authorization
Using the RegLogin Control
Bonus Control: LoginStatus
Summary

Introduction

Unless you have been living on a desert island somewhere, if you are a Microsoft® ASP.NET developer, you have probably heard of a little thing called Whidbey (see ASP.NET "Whidbey" and ASP.NET Whidbey. Whidbey was announced to great fanfare at the 2003 Professional Developer's Conference in Los Angeles, and offers a host of new features that simplify development and reduce the amount of code .NET developers need to write.

One of the features that is surely going to excite ASP.NET developers is the inclusion of a set of server controls that encapsulate much of the code required for registering and authenticating users (Michele Leroux Bustamante writes about these controls in her article, Securing ASP.NET Applications - With Less Code. These controls reduce, and in some cases eliminate, the code you need to write to perform common authentication tasks.

"That sounds great," you say, "but Whidbey isn't out yet. What do I do now?" The answer to that question is that you can, with a little help from this article, write your own registration and login control to encapsulate the code used for these tasks, and provide simplified development and reuse across your applications.

Control Overview

In this article, I will demonstrate how you can create a single control that provides both user registration and authentication using ASP.NET Forms Authentication. For simplicity's sake, this example uses an XML file as the credential store for the control. The control, however, can be easily modified to authenticate credentials against a database or other back-end data store (for example, in order to avoid storing credentials within the web space of the application).

The control consists of two separate sets of UI elements: one for logins, and one for registrations, shown in Figures 1 and 2. The control determines which UI to display on the basis of an internal variable, _mode, and defaults to displaying the login UI.

Aa478962.aspnet-buildcustomreg-01(en-us,MSDN.10).gif

Figure 1. The RegLogin control in Login mode

Aa478962.aspnet-buildcustomreg-02(en-us,MSDN.10).gif

Figure 2. The RegLogin control in Register mode

In addition to displaying the UI elements, the control exposes one public, read-only property called StatusMessage, which is used to communicate information about the success or failure of the login or registration to the consuming page. This allows the developer using the control to choose how and where this information is displayed to the end user. The control also exposes one public method, CreateBlankUsersFile, which can be used to create the XML file used to store credentials for the control. The RegLogin control will automatically call this method internally if it determines at runtime that the file does not exist.

Tip   Since write access is required on the folder where the XML file is created (for this control, the application root); you may want to call CreateBlankUsersFile from a utility page running with administrative credentials using Impersonation to get the file created. Once the file is created, you can assign read/write permissions on the file to the ASPNET user account, which will allow the control to add new credentials when necessary.

Creating the Basic Control

The first step in creating the RegLogin control is to create a new project, using the Web Control Library template, as shown in Figure 3.

Aa478962.aspnet-buildcustomreg-03(en-us,MSDN.10).gif

Figure 3. The Web Control Library template

As thoughtful as it is of Microsoft Visual Studio® to add a template control class for you, I usually end up adding one with the name I want (in my project, RegLogin) and delete the default class. This is simpler than renaming the default class (which requires that you rename both the class and file; otherwise your naming can get a little hard to track and maintain). So delete the WebCustomControl1.vb class, and add a new class using the Web Custom Control template, as shown in Figure 4.

Aa478962.aspnet-buildcustomreg-04(en-us,MSDN.10).gif

Figure 4. The Web Custom Control template

Building the UI

There are a couple of different ways to build a UI in ASP.NET server controls. The simpler technique, which I will demonstrate with this control, is rendering the output directly from the control. A more complicated, but in some cases more powerful, technique is to use composition, in which the UI for a custom server control is built up from a number of pre-existing server controls, each of which is responsible for rendering its own portion of the control's UI. To stay focused, we will leave further discussion of composite controls to other articles (such as MSDN® Magazine's Advanced ASP .NET Server-side Controls and the .NET Framework Developer's Guide documentation on Developing a Composite Control and focus on rendering.

The Web Custom Control Template is designed for creating a rendered control. Here is a look at the template's code (I have removed the attributes to make the code more readable):

Imports System.ComponentModel
Imports System.Web.UI

Public Class WebCustomControl1
    Inherits System.Web.UI.WebControls.WebControl

    Dim _text As String

    Property [Text]() As String
        Get
            Return _text
        End Get

        Set(ByVal Value As String)
            _text = Value
        End Set
    End Property

    Protected Overrides Sub Render(ByVal output As _
       System.Web.UI.HtmlTextWriter)
        output.Write([Text])
    End Sub

End Class

The template includes a couple of useful Imports statements, and contains a class that inherits from the WebControl base class, has a single property (Text), and overrides the Render method of its base class in order to render its output—in this case just the contents of the Text property.

Note   Because Text is a commonly used member name in the .NET Framework, the property name is encased in square brackets, which tells the Microsoft Visual Basic® compiler to use the local version of Text, rather than the one in any referenced assemblies. Alternatively, you could precede the property name with the full namespace of this control. A more common use of square brackets in Visual Basic is to allow the use of member names that are the same as restricted keywords, particularly for legacy code. While surrounding a member name such as Loop with square brackets will allow you to use that name, it is highly recommended that you avoid the use of restricted keywords for class or member names, which can make your code more error-prone and difficult to maintain.

Choosing a Base Class

The default base class in the Web Custom Control template is System.Web.UI.WebControls.WebControl. While this is a reasonably good starting point for many controls, in order to avoid unnecessary complexity and confusion, it is best to inherit from the simplest base class that provides all the functionality you need. Since we want to keep our login control relatively simple, we will change the base class for the control from System.Web.UI.WebControls.WebControl to System.Web.UI.Control, which is the base class from which all ASP.NET server controls ultimately inherit:

Public Class RegLogin
    Inherits System.Web.UI.Control

Overriding Render

For rendered controls, you will override the Render method of the control's base class to render your custom output. Note that if you inherit from a richer control, such as the TextBox, or DropDownList controls, you will also want to call the Render method of the base class at some point, in order to render the output of the base class. Because the Control class has no UI of its own, there is no need to do that for our control.

One thing to remember is that you do not need to have all of your rendering logic directly within the Render method. For our RegLogin control, we will actually just use the Render method to execute a Select Case statement, and call a separate method, depending on the outcome, to render the appropriate output (registration UI or login UI) for our control:

Protected Overrides Sub Render(ByVal output As _
   System.Web.UI.HtmlTextWriter)
   Select Case _mode
      Case RegLoginMode.Register
         DisplayRegUI(output)
      Case RegLoginMode.Login
         DisplayLoginUI(output)
   End Select
End Sub

The Select Case statement evaluates the private member variable _mode, which uses an enumeration for its type, and defaults to RegLoginMode.Login:

Dim _mode As RegLoginMode = RegLoginMode.Login

The enumeration definition (which resides within the same file, but outside the class declaration) is shown below:

Public Enum RegLoginMode
    Register
    Login
End Enum

Once we have figured out which mode we are in, we call either DisplayRegUI or DisplayLoginUI, in each case passing the HtmlTextWriter instance, output, that is passed to the Render method. DisplayRegUI and DisplayLoginUI illustrate two different techniques for building up the output to be rendered to the browser. DisplayRegUI uses the StringBuilder class to assemble a string containing the output to be rendered, writing all of the output at once at the end of the method, and uses standard HTML tags for the output (along with Visual Basic constants such as vbTab for adding whitespace). DisplayLoginUI uses methods of the HtmlTextWriter instance passed to the procedure, as well as static properties of the HtmlTextWriter class to assemble and format the output. Using this technique, the output is rendered little by little, with each call to Write or WriteLine.

Both rendering methods take advantage of some helpful members of the Control and Page classes to facilitate postbacks for the control. The calls to Me.UniqueID returns the name of the control, including any naming containers for that control (in our case, it returns just the name of the RegLogin control).

Writer.WriteAttribute("name", Me.UniqueID)

Both DisplayRegUI and DisplayLoginUI also call Page.GetPostBackEventReference in order to set up the JavaScript necessary to post back the control with the appropriate parameters (control name, etc.). Note that the use of client-side script does mean that this control will not work properly if the user has turned off JavaScript in their browser:

Writer.WriteAttribute("OnClick", "javascript:" & _
   Page.GetPostBackEventReference(Me, "Login"))

One important difference between DisplayLoginUI and DisplayRegUI is that while DisplayRegUI only provides for posting back the contents of its form fields by clicking a button, DisplayLoginUI also may display a link that causes the control to switch to registration mode, based on the _displayRegLink internal member variable. _displayRegLink is a Boolean that is set to True when a user attempts to log in, but the provided username is not found, as we will see later in this article.

Another notable difference between DisplayLoginUI and DisplayRegUI is that DisplayLoginUI renders two textboxes, one each for username and password, while DisplayRegUI adds a third textbox for password confirmation. We will see why this is important in the next section.

A final note on the rendering of the RegLogin control: the code used to render the registration and login UI for this control could be greatly simplified by rendering only the UI elements themselves, rather than the HTML table elements used to align the elements neatly, but that would also result in a rather unsightly control. This highlights a common trade-off in control development: more functionality, flexibility, or improved appearance often comes at the cost of additional code or complexity. Something you should keep in mind when designing your own controls.

Handling Postbacks

To handle the postbacks that our control will generate at runtime, we will need to add an Implements keyword to the class declaration referencing the IPostBackDataHandler and IPostBackEventHandler interfaces:

Public Class RegLogin
    Inherits System.Web.UI.Control
    Implements IPostBackDataHandler, IPostBackEventHandler

IPostBackDataHandler defines two methods, LoadPostData, and RaisePostDataChangedEvent, which are used for loading the data submitted with a postback and responding to changes in data between postbacks, respectively.

Note   RaisePostDataChangedEvent will only be called by the runtime if LoadPostData, which returns a Boolean, returns True. This means that if you care about changes in data between one postback and a subsequent postback, you should store the data.

IPostBackEventHandler defines a single method, RaisePostBackEvent, which is used for determining whether an event should be raised as the result of a given postback, and raising the event, if necessary.

Implementing the interfaces requires that we add the methods defined in the interfaces. The good news is that this is fairly easy to do, even if you do not already know the exact method signature (method name, arguments, and return type) to use. Once you have added the desired interface(s), you can simply select an interface from the Class Name dropdown in the Visual Basic code editor, and then select the desired method name from the Method Name dropdown, as shown in Figure 5. Members for which an implementation exists in the current class are shown in boldface. You can also use this technique to add event handlers for controls that are used in your classes, as well as other method implementations.

Aa478962.aspnet-buildcustomreg-05(en-us,MSDN.10).gif

Figure 5. Adding an implementation for method defined by an interface

In the RegLogin control, we need to handle three postback possibilities:

  • User submits their credentials by clicking the button in the login UI
  • User submits new credentials by clicking the button in the registration UI
  • User clicks the "Register" link after a failed login attempt

We handle all of these possibilities by examining the contents of the __EVENTARGUMENT form field passed with the postback, and taking the appropriate action:

Public Function LoadPostData(ByVal postDataKey As String, _
   ByVal postCollection As _
   System.Collections.Specialized.NameValueCollection) _
   As Boolean _
   Implements System.Web.UI.IPostBackDataHandler.LoadPostData
    Dim newValues As String() = Split(postCollection(postDataKey), ",")
    Dim Result As Boolean = False

    Select Case Page.Request.Form("__EVENTARGUMENT")
        Case "DisplayRegUI"
            _mode = RegLoginMode.Register
        Case "Login"
            Result = LoadLoginPostData(newValues)
        Case "Register"
            _mode = RegLoginMode.Register
            Result = LoadRegisterPostData(newValues)
    End Select

    Return Result
End Function

The __EVENTARGUMENT form field is set by the second parameter passed to the call to Page.GetPostBackEventReference in the DisplayLoginUI and DisplayRegUI methods. Note that if the value of __EVENTARGUMENT is either "DisplayRegUI" or "Register", we set the _mode member variable to RegLoginMode.Register, which causes the registration UI to be rendered. Since _mode defaults to RegLoginMode.Login, we do not need to explicitly set the login mode when the value of __EVENTARGUMENT is "Login".

We only need to process postback data in the "Login" and "Register" cases, so in these cases we also call another method (either LoadLoginPostData or LoadRegisterPostData) to load the postback data into the appropriate member variables for further processing. Note that we split the collection passed into LoadPostData into a string array called newValues that we then pass into the method in which we will actually load the postback data. Also note that both LoadLoginPostData and LoadRegisterPostData return a Boolean, and their return value determines what is returned from LoadPostData (which determines whether RaisePostDataChangedEvent is called).

In LoadLoginPostData, we pull the username into a local variable, pull the password into the _password member variable, and then compare the local username variable to the _userName member variable to see if the username has changed since the last postback. If it has, we assign the new value to _userName, and return True to indicate that RaisePostDataChangedEvent should be called. In our case, we do not actually do anything useful in RaisePostDataChangedEvent, but this example shows how it operates.

Note   We are able to compare the value of the member variable _userName to the new value passed in because we save the value of _userName and restore it with each postback, by overriding the SaveViewState and LoadViewState methods of the Control base class:
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
    If Not savedState Is Nothing Then
        _userName = CStr(savedState)
    End If
End Sub

Protected Overrides Function SaveViewState() As Object
    Return _userName
End Function

In LoadRegisterPostData, we pull the username, password, and password confirm values from the postback into the appropriate member variables, and return False. We return False because we would not expect the user to change their username when re-submitting the registration information. Of course, it is possible they might, so this is a place where you might want to add some logic to check for this and take appropriate action. I have left this out to keep things simple.

Once we have loaded the data from the postback into the appropriate local variables, we will need to either retrieve and verify the credentials against those stored in our XML file (in the case of a postback from the login UI), or save the new username and password (in the case of a postback from the registration UI). We will discuss these in the next section.

Verifying and Saving Credentials

The starting point for verifying and saving credentials is the RaisePostBackEvent method, which is called automatically by the runtime on the control that causes a postback, assuming it implements IPostBackEventHandler. When using JavaScript to post back the page, as in the case of the "Register" link, the eventArgument parameter passed to RaisePostBackEvent by the runtime contains the name of the argument parameter passed to Page.GetPostBackEventReference, which allows us to determine if it was the "Register" link that caused the postback.

Since one of the purposes of RaisePostBackEvent is to raise events to the page that contains the control, we will need to define the events we intend to raise from our control, which we do with the following event declarations:

'Event declarations
Public Event AuthSuccess As EventHandler
Public Event AuthFailure As EventHandler
Public Event RegSuccess As EventHandler
Public Event RegFailure As EventHandler

Once the events have been declared, we can raise them using the RaiseEvent keyword, as we will see shortly.

In RaisePostBackEvent, we first examine the _mode member variable to determine if we are in Register or Login mode. For Register mode, we determine whether the "Register" link caused the postback. If so, we perform no further processing, since the purpose of the postback in this case is simply to switch from displaying the login UI to displaying the registration UI. If the postback was not the result of the "Register" link being clicked, we attempt to save the provided credentials by calling the SaveCredentials method.

The SaveCredentials method creates a new DataSet instance (we do not need to specify the System.Data namespace in which the DataSet class resides because this namespace is automatically imported at the project level in all new Visual Basic .NET projects), reads in the current credentials file (using the DataSet's ReadXml method), and then attempts to save the credentials passed to it by RaisePostBackEvent as a new row, and if successful, saves the credentials back to the XML file using the DataSet's WriteXml method. If the save is successful, SaveCredentials returns True. If the username already exists in the credentials file, or the passwords provided do not match, then the credentials are not saved, and SaveCredentials returns False. We also set the _statusMessage member variable (which is exposed as the public property StatusMessage) to provide a helpful indication of the reason for the failure of registration. The page that consumes the control can use the StatusMessage property to provide this message to the end user. Note that the password is stored as an MD5 hash using the HashPasswordForStoringInConfigFile method of the FormsAuthentication helper class (the Imports System.Web.Security line at the top of RegLogin.vb allows us to call this method without explicitly calling out the namespace).

Note   StatusMessage in this example is exposed as a property to demonstrate the setting and exposing of a property on a custom server control. The downside to this technique for the StatusMessage property is that if the consuming page does not read and display the StatusMessage property, the end user will not know why their registration (or login) failed. For this reason, you might want to render the status message directly from the control if you want to ensure that the message is always displayed.

If SaveCredentials returns True, we raise the RegSuccess event and switch the control to login mode, otherwise we raise the RegFailure event, and keep the control in register mode (to give the user the opportunity to correct the problem with their registration.

The VerifyCredentials method, which we call from RaisePostBackEvent when we are in login mode, performs similar logic to the SaveCredentials method. It first creates a new DataSet, then attempts to read in the credentials from the credentials file. Since there is a possibility that there might not be a credentials file available the first time someone attempts to login, the attempt is wrapped with a Try...Catch block that can catch a FileNotFoundException, and call CreateBlankUsersFile to create the file from scratch (note that if the ASP.NET user does not have write permissions on the root of the application, this call will throw an exception).

If the ReadXml call is successful, we use the Select method exposed by the DataTable class (each DataSet contains one or more DataTable objects) to locate the row, if any, that matches the provided username. If a match is found, we hash the password provided by the user and compare it to the stored hash for that username. If they match we return True, and provide a status message to that effect. If either the username or password do not match, we return False, and set _statusMessage to an appropriate value. In the case of a username mismatch, we also set the _displayRegLink member variable to True, which will cause the "Register" link to be rendered, allowing the user to switch to register mode.

As with SaveCredentials, the return value from VerifyCredentials determines the event to be raised. If False, we raise the AuthFailure event. If True, we raise the AuthSuccess event, and call the SetAuthCookie method of the FormsAuthentication class to set the authentication cookie that will allow the user to access restricted content.

That completes the control, so we can now compile the control, and then prepare to use it in a page. There are several steps involved in this preparation:

  • Configuring the application for Forms Authentication
  • Restricting access to pages or content
  • Using the control within a login page

Configuring Forms Authentication and Authorization

The first thing we will do is create a new Web Application project, and then set up Forms Authentication by modifying the <authentication> section of the Web.config file at the root of the application (this file is added automatically when you create a new ASP.NET Web Application project) to look like the following:

<authentication mode="Forms">
   <forms
      loginUrl="Login.aspx"
      protection="All">
   </forms>
</authentication>

This tells ASP.NET to use Forms Authentication with this application (the default is Windows Authentication), that the name of the login page for the application is Login.aspx (this also happens to be the default, but it never hurts to be explicit), and to both encrypt the authentication cookie and validate that its contents have not been altered in transit.

Once Forms Authentication is configured, we use the <authorization> element to explicitly provide or restrict access to resources, based on usernames, groupnames, or wildcards ("*" for all users and/or "?" for anonymous users). If we add the <authorization> element to the Web.config file at the root of the application, the restriction(s) will apply to all files handled by ASP.NET within the application. Since our sample application will contain pages that we want all users to be able to access, we will start with the following in Web.config, which is the default:

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

Then, we will add the following, which prevents anonymous users from accessing the file ProtectMe.aspx. The <location> element provides the ability to apply configuration elements to a specific file or directory specified by its path attribute. You can also add an allowOverride attribute to specify whether the elements contained within the <location> element can be overridden by Web.config files in subdirectories of the application:

<location path="ProtectMe.aspx">
   <system.web>
      <authorization>
         <deny users="?"/>
      </authorization>
   </system.web>
</location>

The above configuration section needs to be placed inside the <configuration> element of Web.config, but outside the <system.web> element (since it defines its own <system.web> element).

The last important configuration task is protecting the credentials file, which requires two steps. The first step is required because ASP.NET does not natively handle .xml files. As such, we need to tell IIS to pass requests for .xml files to ASP.NET, so that we can secure them using an HttpHandler provided for the purpose of preventing download of certain file types. To configure ASP.NET to handle .xml files, follow these steps:

  1. Open the Internet Information Services admin tool in Administrative Tools (you must either be an administrator, or run Internet Services Manager using Run As... with admin credentials to complete these steps) and navigate to the Web Application project for this example.
  2. Right-click the application folder, and select Properties. The Properties dialog will be displayed.
  3. Click the Configuration button. The Application Configuration dialog will be displayed.
  4. Click the Add button. In the Add/Edit Application Extension Mapping dialog, browse to the path of the aspnet_isapi.dll file (for ASP.NET 1.1, the default is C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll), set the extension to ".xml", and then click OK. Click OK twice more to close the remaining dialogs.

Once the application mapping for .xml files has been set up, we can prevent the credentials file from being downloaded by adding the following section to Web.config:

<httpHandlers>
   <add verb="*" path="Users.xml" 
      type="System.Web.HttpForbiddenHandler"/>
</httpHandlers>

This tells ASP.NET to refuse any requests for the file Users.xml by assigning the HttpForbiddenHandler to that filename.

Using the RegLogin Control

Now that our client application is configured, we will take a look at how the control is used in a page. First, we will add a login page to the Web Application project by right-clicking the project and selecting Add > Add Web Form, and naming the file Login.aspx. With Login.aspx open in Design mode, let us take a moment to add the RegLogin control to the Visual Studio .NET toolbox, using the following steps:

  1. Open the Toolbox by hovering over the tab with your mouse pointer.
  2. Select the Web Forms Toolbox palette, if it is not already selected, and right-click inside the Web Forms palette and select Add/Remove Items...
  3. In the Customize Toolbox dialog, shown in Figure 6, click the Browse... button.

    Aa478962.aspnet-buildcustomreg-06(en-us,MSDN.10).gif

    Figure 6. The Customize Toolbox dialog

  4. Browse to the assembly containing the control (by default this will be the bin directory of the control project), select the assembly, and click the Open button. Click OK to close the Customize Toolbox dialog. The control should now appear in the Toolbox, as shown in Figure 7.

    Aa478962.aspnet-buildcustomreg-07(en-us,MSDN.10).gif

    Figure 7. The RegLogin control in the Toolbox

Now we are ready to use the control. To simplify the layout of the page, use the Properties window to change the pageLayout property (you may need to select the DOCUMENT object in the drop-down list to access this property) to FlowLayout. Then, simply double-click the RegLogin control in the toolbox, and it will be added to the page (note that when you add a custom control from the Toolbox, the @ Register directive that is required to use the control is automatically added for you...you can view the @ Register directive by switching to HTML view). Place the cursor after the control and click Enter to add a line break, and then add a Label control to the page (for the status message) by either double-clicking the Label control in the Toolbox, or dragging it onto the page. Next, we will add event handlers for the events exposed by the control.

Right-click Login.aspx and select View Code. This will load the codebehind module for the page into the code editor. In the Page_Load event handler, we will set the Visible property for the Label control to False, since we do not want the label to be displayed unless we have a message to display:

Private Sub Page_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
    Label1.Visible = False
End Sub

Next, we will add event handlers for each of the events that are raised by the RegLogin control. Remember that you can use the Class Name and Member Name drop-downs to easily insert the signature for events and methods for the controls in a page:

Private Sub RegLogin1_AuthSuccess(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles RegLogin1.AuthSuccess
    If Not Request.QueryString("ReturnUrl") Is Nothing Then
        DoRedirect = True
    End If
End Sub

Private Sub RegLogin1_AuthFailure(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles RegLogin1.AuthFailure
    Label1.ForeColor = System.Drawing.Color.Red
    Label1.Text = RegLogin1.StatusMessage
    Label1.Visible = True
End Sub

Private Sub RegLogin1_RegSuccess(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles RegLogin1.RegSuccess
    Label1.ForeColor = System.Drawing.Color.Green
    Label1.Text = RegLogin1.StatusMessage
    Label1.Visible = True
End Sub

Private Sub RegLogin1_RegFailure(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles RegLogin1.RegFailure
    Label1.ForeColor = System.Drawing.Color.Red
    Label1.Text = RegLogin1.StatusMessage
    Label1.Visible = True
End Sub

In the case of all the events except for AuthSuccess (where we do a little extra), we set the ForeColor of the Label control to either red (for failure) or green (for success), set the Text of the label to the StatusMessage property of the RegLogin control, and set the label's Visible property to True.

The AuthSuccess event is a special case. When a user requests a page that is restricted by ASP.NET authorization in an application protected by Forms Authentication, they are automatically redirected to the configured login page. Once they have successfully authenticated, we want to redirect the user to the page that they originally requested. This can be a little tricky for a couple of reasons. First, we have to make sure that the login page was not the page originally requested by the user. This is accomplished by testing whether the ReturnUrl querystring variable is null (Is Nothing) in the AuthSuccess event handler. The second tricky bit is that if we try to redirect to the original page from within the AuthSuccess event handler itself, the redirect will not succeed. Instead, we add a Boolean, DoRedirect, to the codebehind module, and set its value to True when the AuthSuccess method is fired, assuming that ReturnUrl is not null. Then we add a handler for the PreRender event, which will fire after the control events, but before the page is rendered, and perform the redirect there, assuming that we have set DoRedirect to True.

If ReturnUrl is null, we simply set the status message and show the label.

To test the login page, we will need a protected page, which in the sample files is called ProtectMe.aspx. ProtectMe.aspx consists of a Label control, which is set to display the name of the logged in user in Page_Load, and an instance of the LoginStatus control (see the section below entitled Bonus Control: LoginStatus), which provides the user with the ability to log out once they have logged in. Also included in the project is a page called Unprotected.aspx, which demonstrates the use of the LoginStatus control to log in, as well as log out.

To test the login page, first ensure that both the control project and the Web Application project have been compiled. Then, right-click ProtectMe.aspx and select Browse With... and choose Microsoft Internet Explorer and click Browse. You will be redirected to the login page, which should look similar to Figure 8.

Aa478962.aspnet-buildcustomreg-08(en-us,MSDN.10).gif

Figure 8. Redirected to the login page

Attempt to log in using any username and password. If you receive a security exception, you will need to temporarily provide write access for the ASPNET account to the root of the web application, repeat the previous steps, and then remove write access for the root of the application.

When you attempt to log in using a username that does not exist, you should be prompted to register, as shown in Figure 9. If you click the Register link, the page will post back, and the control will switch to register mode, as shown in Figure 10 (note that the username you entered is preserved).

Aa478962.aspnet-buildcustomreg-09(en-us,MSDN.10).gif

Figure 9. Prompting for registration

Aa478962.aspnet-buildcustomreg-10(en-us,MSDN.10).gif

Figure 10. The registration UI

If you provide a unique username and password (and password confirmation), and click Submit, the display should look similar to Figure 11.

Aa478962.aspnet-buildcustomreg-11(en-us,MSDN.10).gif

Figure 11. Successful registration

Now, attempt to log in using the username and password you supplied (or you can use the username "TempUser," and "password" as the password, which are added as a default when the credentials file is created), and the result should look similar to Figure 12 (note that we have been redirected to the page originally requested).

Aa478962.aspnet-buildcustomreg-12(en-us,MSDN.10).gif

Figure 12. Result of a successful login

Bonus Control: LoginStatus

In addition to the RegLogin control, the sample code for this article contains a bonus control, the LoginStatus control, which is a simplified version of the control of the same name that is included in ASP.NET Whidbey. The RegLogin.LoginStatus control queries the Page.User.Identity.IsAuthenticated property to determine whether the current user is logged in or not, and displays either a hyperlink that redirects to the application's login page (configurable via the control's LoginUrl property), if the user is not logged in, or a hyperlink that causes a postback in which the control calls the SignOut method of the FormsAuthentication helper class and redirects to the current URL (causing the user to automatically be directed to the login page if the page they are currently viewing does not allow anonymous access). The LoginStatus control also contains a designer class that demonstrates a simple example of using the GetDesignTimeHtml method of the ControlDesigner class to customize how your control is displayed on the Visual Studio .NET design surface.

Summary

In this article, we have looked at the process for creating a reusable login and registration control, including reading from and writing to an XML credentials file, hashing passwords, using Forms Authentication, configuring authorization, handling postbacks and raising events, and protecting files as desired. Using the techniques explored in this article, you can create a consistent, reusable UI for authenticating users within your applications, and one that does not require you to wait for Whidbey to arrive.

The sample code for this article contains a Visual Studio .NET project for both the RegLogin and LoginStatus controls, and for the Web Application that consumes the controls. To run the RegLoginClient application, you will need to copy the files to your computer, then create an IIS application (virtual root), and map it to the folder where you copied the files.

About the author

G. Andrew Duthie is the founder and principal of Graymad Enterprises, Inc., providing training and consulting in Microsoft Web development technologies. Andrew has been developing multi-tier Web applications since the introduction of Active Server Pages. He has written numerous books on ASP.NET including: Microsoft ASP.NET Step By Step, Microsoft ASP.NET Programming with Microsoft Visual Basic, and ASP.NET in a Nutshell. Andrew is a frequent speaker at events including Software Development, the Dev-Connections family of conferences, Microsoft Developer Days, and VSLive! He also speaks at .NET user groups as a member of the International .NET Association (INETA) Speaker's Bureau. You can find out more about Andrew at his company's Web site, Graymad Enterprises, Inc..

Show: