When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, May 3, 2006

Examining ASP.NET's Membership, Roles, and Profile - Part 4

By Scott Mitchell


A Multipart Series on ASP.NET's Membership, Roles, and Profile
This article is one in a series of articles on ASP.NET's membership, roles, and profile functionality.

  • Part 1 - learn about how the membership features make providing user accounts on your website a breeze. This article covers the basics of membership, including why it is needed, along with a look at the SqlMembershipProvider and the security Web controls.
  • Part 2 - master how to create roles and assign users to roles. This article shows how to setup roles, using role-based authorization, and displaying output on a page depending upon the visitor's roles.
  • Part 3 - see how to add the membership-related schemas to an existing database using the ASP.NET SQL Server Registration Tool (aspnet_regsql.exe).
  • Part 4 - improve the login experience by showing more informative messages for users who log on with invalid credentials; also, see how to keep a log of invalid login attempts.
  • Part 5 - learn how to customize the Login control. Adjust its appearance using properties and templates; customize the authentication logic to include a CAPTCHA.
  • Part 6 - capture additional user-specific information using the Profile system. Learn about the built-in SqlProfileProvider.
  • Part 7 - the Membership, Roles, and Profile systems are all build using the provider model, which allows for their implementations to be highly customized. Learn how to create a custom Profile provider that persists user-specific settings to XML files.
  • Part 8 - learn how to use the Microsoft Access-based providers for the Membership, Roles, and Profile systems. With these providers, you can use an Access database instead of SQL Server.
  • Part 9 - when working with Membership, you have the option of using .NET's APIs or working directly with the specified provider. This article examines the pros and cons of both approaches and examines the SqlMembershipProvider in more detail.
  • Part 10 - the Membership system includes features that automatically tally the number of users logged onto the site. This article examines and enhances these features.
  • Part 11 - many websites require new users to verify their email address before their account is activated. Learn how to implement such behavior using the CreateUserWizard control.
  • Part 12 - learn how to apply user- and role-based authorization rules to methods and classes.
  • Part 13 - see how to create a login screen that allows Admin users to log in as another user in the user database.
  • Part 14 - learn how to create a page that permits users to update their security question and answer.
  • Part 15 - the Membership API does not provide a means to change a user's username. But such functionality is possible by going directly to the user store, as this article illustrates.
  • Part 16 - the Membership system includes the necessary components for enforcing expiring passwords. This installment shows how to implement such a policy.
  • Part 17 - see how to display important, unread announcements to users when they sign into the website.
  • Part 18 - often, applications need to track additional user information; learn how to capture this information in a database and see how to build pages to let users update their own information and to display this information to others.
  • (Subscribe to this Article Series! )

    Introduction


    The ASP.NET Membership class provides a ValidateUser(userName, password) method that returns a Boolean value indicating whether or not a user's supplied credentials are valid. This method is automatically utilized from the Login Web control and can also be used programmatically, if needed. In the Membership system, there are multiple scenarios by which a user's credentials can be invalid:
    • The username supplied might not exist in the membership directory
    • The username may exist, but the supplied password might be incorrect
    • The username and password may be correct, but:
      • The user may not yet be approved
      • The user may be locked out; this can happen if the user attempts to login with an invalid password for a specified number of tries (five, by default)
    Unfortunately, the ValidateUser(userName, password) method just returns False if the credentials are invalid, and does not include information as to why, exactly, the credentials are invalid. For the Login control, when ValidateUser(userName, password) returns False the message, "Your login attempt was not successful. Please try again." is displayed, by default. If a user is locked out or their account not yet approved, such a message - which will be shown even in the face of the correct username and password - can easily lead to a confused and frustrated user.

    In this article we'll see how to provide additional feedback during the login process to help alleviate any such confusion. Moreover, we'll see how to audit invalid logins and present the data in a report. Read on to learn more!

    - continued -

    Approved and Locked Out User Accounts


    The user accounts in the ASP.NET Membership system can be accessed and modified programmatically through the Membership and MembershipUser classes. Membership contains a bevy of static methods that offer the ability to retrieve information about all users, a particular user, to update a user, and so on, while the MembershipUser class contains properties that describe the state of a specific user (UserName, Email, LastLoggedOnDate, and so on).

    The Membership system enables user accounts to be marked as inactive (not approved) and locked out. When creating a new user account, the account is, by default, approved. However, in some scenarios you might want to have an administrator manually approve new accounts before they become active, or have the user progress through some automated process (like clicking on a verification link sent through email). In either case, the newly created user would be marked as inactive. Such a user cannot log in to the site, as the ValidateUser(userName, password) method will always return False, regardless of whether they entered their correct credentials.

    Since the process of authenticating through a forms-based scheme involves simply sending the user's credentials over an HTTP request, an attacker could attempt to break into a user's account by writing a script that looped through a dictionary of common passwords, sending an appropriately formatted HTTP request for a particular user for each password in the dictionary. To help stop such attacks, the Membership system automatically locks out a user if a certain number of invalid password attempts have transpired in a specified window of time. These settings default to five invalid password attmepts within a ten minute window, but can be customized in Web.config if needed (refer to Part 1 of this article series for information on customizing the membership provider). As with unapproved users, a locked user cannot log in to the website regardless of whether or not they provide their credentials. In order to unlock a user account, the MembershipUser class's UnlockUser() method must be invoked.

    When attempting to login throught the Login Web control, a user will see the same message whether they are unable to log in due to an invalid username, an invalid password, or because their account has not been approved or is currently locked out. Rather than showing the user the same blanket message, we can customize the login page to display a more appropriate message.

    Displaying an Informative Message in the Face of an Invalid Login


    When a user attempts to log on to the website using the Login Web control, the Login control's LoginError event fires. This event handler is not passed any information that explains why the login failed; however, we can get the username and password the user attempted to use via the Login control's UserName and Password properties. Using the UserName property, we can get information about the user account through the Membership.GetUser(userName) method. This method returns a MembershipUser object, from which we can check the IsApproved and IsLockedOut properties to determine why the user's credentials were deemed invalid.

    The following event handler code shows how to accomplish this. The resulting help message is displayed in the Label Web control named LoginErrorDetails; you can view the Login page's complete code and declarative markup by downloading the complete code at the end of this article.

    Protected Sub Login1_LoginError(ByVal sender As Object, ByVal e As System.EventArgs) Handles Login1.LoginError
        'There was a problem logging in the user

        'See if this user exists in the database
        Dim userInfo As MembershipUser = Membership.GetUser(Login1.UserName)

        If userInfo Is Nothing Then
            'The user entered an invalid username...
            LoginErrorDetails.Text = "There is no user in the database with the username " & Login1.UserName
        Else
            'See if the user is locked out or not approved
            If Not userInfo.IsApproved Then
                LoginErrorDetails.Text = "Your account has not yet been approved by the site's administrators. Please try again later..."
            ElseIf userInfo.IsLockedOut Then
                LoginErrorDetails.Text = "Your account has been locked out because of a maximum number of incorrect login attempts. You will NOT be able to login until you contact a site administrator and have your account unlocked."
            Else
                'The password was incorrect (don't show anything, the Login control already describes the problem)
                LoginErrorDetails.Text = String.Empty
            End If
        End If
    End Sub

    With this code, the user will see a more informative message if their login fails. The following screenshots show the results when attempting to login with Bruce (whose account has been locked out) and Alfred (whose account has yet to be approved). Without the above event handler, these users would have just seen the standard "Your login attempt was not successful. Please try again." message when attempting to login, even had they entered the correct credentials (due to their locked out / approved status). (Clearly this would be confusing to Bruce and Alfred, who might not realize that their account has yet to been approved or has been locked out.)

    Bruce's account has been locked out, and he sees an appropriate message.

    Alfred's account has yet to be approved, and he sees an appropriate message.

    Logging Invalid Login Attempts


    While the ASP.NET Membership system keeps track of invalid password attempts and locks out a user's account if a specified threshold is surpassed, it doesn't log any of the invalid login attempts. Such a log can provide a quick report to see what users have had troubles logging in, what users are logged out, and which ones have yet to be approved. This data can also help in a security audit, identifying patterns that might be attackers who are relying on some sort of dictionary attack.

    To capture such information, I created a database table named InvalidCredentialsLog in the membership database with the following schema:

    InvalidCredentialsLog
    ColumnData TypeComments
    InvalidCredentialsLogIDint, PK, IDENTITY, NOT NULLUniquely identifies each record
    UserNamenvarchar(256), NOT NULLThe name entered by the user, when logging in
    Passwordnvarchar(128)The password attempted by the user; only recorded if the user enters an incorrect password or the username supplied does not exist in the database (i.e., does not appear for users entering valid credentials, but who are locked out or not approved)
    IsApprovedbitWhether or not the user account is approved
    IsLockedOutbitWhether or not the user account is locked out
    IPAddressvarchar(15)The IP Address of the user who supplied invalid credentials
    LoginAttemptDatedatetime, NOT NULLThe date/time the invalid login attempt occurred (defaults to getdate())

    The Membership system includes an ApplicationID, which allows multiple applications to store their user account information in a single database. Ideally, this table would include the ApplicationID and the report would only show those invalid credentials for the current application (assuming you use a single membership store for multiple applications). I leave adding this feature as an exercise for the reader!

    Next, I created a stored procedure - InvalidCredentialsLog_Insert - that takes in the user's username, password, and IP address. It then checks to see if the username maps to a user in the aspnet_Users table and, if so, grabs the user's IsApproved and IsLockedOut fields values. It then INSERTs this information into the InvalidCredentialsLog_Insert table.

    When the user enters invalid credentials in the login page, this stored procedure needs to be called, passing in the user's information. To accomplish this I created a SqlDataSource (InvalidCredentialsLogDataSource) and configured it to call the stored procedure. Then, I extended the Login Web control's LoginError event handler to set the parameters for this stored procedure and invoke it:

    Protected Sub Login1_LoginError(ByVal sender As Object, ByVal e As System.EventArgs) Handles Login1.LoginError
        'Set the parameters for InvalidCredentialsLogDataSource
        InvalidCredentialsLogDataSource.InsertParameters("ApplicationName").DefaultValue = Membership.ApplicationName
        InvalidCredentialsLogDataSource.InsertParameters("UserName").DefaultValue = Login1.UserName
        InvalidCredentialsLogDataSource.InsertParameters("IPAddress").DefaultValue = Request.UserHostAddress

        'The password is only supplied if the user enters an invalid username or invalid password - set it to Nothing, by default
        InvalidCredentialsLogDataSource.InsertParameters("Password").DefaultValue = Nothing


        'There was a problem logging in the user
        'See if this user exists in the database
        Dim userInfo As MembershipUser = Membership.GetUser(Login1.UserName)
        If userInfo Is Nothing Then
            'The user entered an invalid username...
            LoginErrorDetails.Text = "There is no user in the database with the username " & Login1.UserName

            'The password is only supplied if the user enters an invalid username or invalid password
            InvalidCredentialsLogDataSource.InsertParameters("Password").DefaultValue = Login1.Password

        Else
            'See if the user is locked out or not approved
            If Not userInfo.IsApproved Then
                LoginErrorDetails.Text = "Your account has not yet been approved by the site's administrators. Please try again later..."
            ElseIf userInfo.IsLockedOut Then
                LoginErrorDetails.Text = "Your account has been locked out because of a maximum number of incorrect login attempts. You will NOT be able to login until you contact a site administrator and have your account unlocked."
            Else
                'The password was incorrect (don't show anything, the Login control already describes the problem)
                LoginErrorDetails.Text = String.Empty

                'The password is only supplied if the user enters an invalid username or invalid password
                InvalidCredentialsLogDataSource.InsertParameters("Password").DefaultValue = Login1.Password
            End If
        End If

        'Add a new record to the InvalidCredentialsLog table
        InvalidCredentialsLogDataSource.Insert()
    End Sub

    In addition to this stored procedure and event handler, I build a simple report page that shows all of the invalid credentials in a pageable, sortable GridView, along with summary information, as shown in the screen shot below. This report page, along with the stored procedure and a working demo, can be downloaded at the end of this article.

    The invalid login information is presented in a simple report.

    For popular websites, the InvalidCredentialsLog table could grow very large. It might make sense to instigate some policy that involved deleting invalid entries older than a certain date (such as a SQL Server Job that ran weekly, deleting the audit history older than, say, three months). The report shown in the screen shot above is very simple and has not been optimized for working with large result sets. It currently uses default paging, bringing back every record from the database for each page of data shown. For significantly large InvalidCredentialsLog tables this could introduce a less than ideal page load time. Consider upgrading the paging logic used here to use custom paging, which intelligently grabs only those records needed for displaying the current page of data; see Custom Paging in ASP.NET 2.0 with SQL Server 2005 for more information.

    Conclusion


    In this fourth part of the Membership, Roles, and Profile article series, we saw how to enhance the login process by including more descriptive information for those users attempting to login whose accounts are not yet approved or have been locked out. This level of extensibility is due in part to the Membership API, which can be accessed programmatically, declaratively (through data source controls), and through Web controls (such as the Login Web control). The audit table presented in this article could be expanded to capture not just invalid logins, but also valid ones.

    Be sure to check out the download available at the end of this article. It includes the complete source code and database additions discussed throughout this article. Additionally, it includes an Admin page that provides a GridView that lists the users in the system and allows the user to quickly toggle their approved status, to unlock locked out users, and to toggle whether or not a given user is in the Administrator role. (Only users in the Administrator role can view the Admin-related web pages).

    Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the code used in this article

    A Multipart Series on ASP.NET's Membership, Roles, and Profile
    This article is one in a series of articles on ASP.NET's membership, roles, and profile functionality.

  • Part 1 - learn about how the membership features make providing user accounts on your website a breeze. This article covers the basics of membership, including why it is needed, along with a look at the SqlMembershipProvider and the security Web controls.
  • Part 2 - master how to create roles and assign users to roles. This article shows how to setup roles, using role-based authorization, and displaying output on a page depending upon the visitor's roles.
  • Part 3 - see how to add the membership-related schemas to an existing database using the ASP.NET SQL Server Registration Tool (aspnet_regsql.exe).
  • Part 4 - improve the login experience by showing more informative messages for users who log on with invalid credentials; also, see how to keep a log of invalid login attempts.
  • Part 5 - learn how to customize the Login control. Adjust its appearance using properties and templates; customize the authentication logic to include a CAPTCHA.
  • Part 6 - capture additional user-specific information using the Profile system. Learn about the built-in SqlProfileProvider.
  • Part 7 - the Membership, Roles, and Profile systems are all build using the provider model, which allows for their implementations to be highly customized. Learn how to create a custom Profile provider that persists user-specific settings to XML files.
  • Part 8 - learn how to use the Microsoft Access-based providers for the Membership, Roles, and Profile systems. With these providers, you can use an Access database instead of SQL Server.
  • Part 9 - when working with Membership, you have the option of using .NET's APIs or working directly with the specified provider. This article examines the pros and cons of both approaches and examines the SqlMembershipProvider in more detail.
  • Part 10 - the Membership system includes features that automatically tally the number of users logged onto the site. This article examines and enhances these features.
  • Part 11 - many websites require new users to verify their email address before their account is activated. Learn how to implement such behavior using the CreateUserWizard control.
  • Part 12 - learn how to apply user- and role-based authorization rules to methods and classes.
  • Part 13 - see how to create a login screen that allows Admin users to log in as another user in the user database.
  • Part 14 - learn how to create a page that permits users to update their security question and answer.
  • Part 15 - the Membership API does not provide a means to change a user's username. But such functionality is possible by going directly to the user store, as this article illustrates.
  • Part 16 - the Membership system includes the necessary components for enforcing expiring passwords. This installment shows how to implement such a policy.
  • Part 17 - see how to display important, unread announcements to users when they sign into the website.
  • Part 18 - often, applications need to track additional user information; learn how to capture this information in a database and see how to build pages to let users update their own information and to display this information to others.
  • (Subscribe to this Article Series! )



  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article