RSS
Add to Technorati Favorites
Follow me on Twitter


Becky Bertram's Blog > Posts > Notifying Users Why Their Login Failed in the ASP.NET Login Control
Notifying Users Why Their Login Failed in the ASP.NET Login Control
As a follow-on to my last post on using FBA sites in SharePoint, I wanted to point out an additional enhancement you can make, which will help out your users.
 
When a user tries to log into your Web site using the Login control, unfortunately, it often just tells the user a bland message such as "invalid login". That doesn't give the user enough information. Is their username wrong? Is the password wrong? Are the username and password correct but the user's been locked out? Users have no idea what the true problem is. I found a way around this by manually validating the user, discovering any error messages, and sending those to the user, and I'd like to show you how to do this as well.
 
The most difficult part of this is that if a user is using the wrong password, your Membership Provider will increment a field called FailedPasswordAttemptCount in the Membership table in your database. Once the user has reached the maximum number of failed attempts (as specified by the MaximumInvalidPasswordAttempts attribute of your provider node), the user's account will be locked.
 
If you use a method such as Membership.ValidateUser(), that field will be queried in the database. Unfortunately, though, I don't know of a good way to access that value directly from the MembershipUser object, the way you can find out things like MembershipUser.IsLockedOut.
 
To get around this, I simply created a new stored procedure that I added to my membership database. (I'm not using multiple application names in my web application, so I'm doing a "poor man's" query of simply retrieving a user by the user name they typed into the login box.) Here's the stored procedure:
 
CREATE PROCEDURE [dbo].[aspnet_Membership_GetFailedPasswordAttemptCount]
    @UserName  nvarchar(256)
AS
 DECLARE @UserId  uniqueidentifier
 DECLARE @FailedPasswordAttemptCount  int
 
 SELECT  @UserId = u.UserId
 FROM    dbo.aspnet_Users u
 WHERE   @UserName = u.LoweredUserName
   
 SELECT FailedPasswordAttemptCount
 FROM aspnet_Membership
 WHERE UserId = @UserId
 
Then, I simply added a method that executes this stored procedure:
 
private int GetFailedPasswordAttemptCount(string userName)
{
    SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["FBAConnectionString"].ConnectionString);
    SqlCommand cmd = new SqlCommand("aspnet_Membership_GetFailedPasswordAttemptCount", conn);
    cmd.CommandType = System.Data.CommandType.StoredProcedure;
    cmd.Parameters.Add("@Username", System.Data.SqlDbType.VarChar, 255).Value = userName;
    int failedAttempts = 0;
    try
    {
        conn.Open();
        failedAttempts = (int)cmd.ExecuteScalar();
        conn.Close();
    }
    catch
    {
        throw;
    }
    finally
    {
        if (conn.State != System.Data.ConnectionState.Closed)
        {
            conn.Close();
        }
    }
    return failedAttempts;
}

 
Finally, I create a custom event handler for the Authenticate event on the ASP.NET Login control I'm using.
 
public void loginCtrl_Authenticate(object sender, AuthenticateEventArgs e)
{
    //Manually validate the user. If they're valid, then they authenticate just fine and get logged in.
    bool isValid = Membership.ValidateUser(loginCtrl.UserName, loginCtrl.Password);
    if (isValid)
    {
        e.Authenticated = true;
    }
    else
    {
        MembershipUser user = Membership.GetUser(loginCtrl.UserName);
        //First, check to see if the username was found in the Membership database
        if (user != null)
        {
            //Next, find out if the user has been locked out.
            if (user.IsLockedOut)
            {
                //If the user's account is locked out, notify them.
                e.Authenticated = false;
                loginCtrl.FailureText = "Your account is locked out. Please contact an administrator.";
            }
            //If the user isn't locked out, it means their password is wrong. Notify them how many times they have entered a false password, and how many times they can have a failed login before they're locked out.
            else
            {
                e.Authenticated = false;
                int failedAttempts = GetFailedPasswordAttemptCount(user.UserName);
                string errorMsg = "Your password is incorrect. Your login has failed {0} times. After {1} failed logins you will be locked out.";
                loginCtrl.FailureText = String.Format(errorMsg, failedAttempts, Membership.MaxInvalidPasswordAttempts);
            }
        }
        else
        {
            e.Authenticated = false;
            loginCtrl.FailureText = "The username cannot be found.";  
        }
    }
}

 
Finally, simple register the event handler with the Login control's Authenticate event:
 
loginCntrl.Authenticate += new AuthenticateEventHandler(loginCtrl_Authenticate);
 
In my case, my Login control is hosted in a Web Part. Even if your control is in an ASP.NET login page you've created, you'll simply add the last several methods to whatever control or page is hosting your Login control.

Comments

Security issues with this

I don't think you should tell the user if they entered the wrong username or password. It opens up a possibility for "evil" users to find a correct user name, and then they can start trying passwords, right?

Good point, Wictor

You have a good point, Wictor. However, I think all depends on just *how* secure the data should be.

In my case, I'm working with an application that does need to be secure, but it's not like it's storing financial records or national secrets. It's an application that allows customers to respond to RFP's from my client. The fact is that the end-user base is not exactly computer-savvy, so my client gets LOTS of phone calls regarding "why can't I log in?" In my specific case, the risk of some rogue hacker wanting to hack usernames and passwords seems far less than the time and money that would be saved by my customers in the reduced number of tech support calls.

Bottom line, you're correct that it does open up a security risk. However, in any application, there's usually a monetary cost associated with increased security. In this case, the reduced cost outweighs the possible risk, for my client.
 

Comment Title *


Your Name


Body *


Your E-mail Address


Your Website

Type the Web address: (Click here to test)  

Type the description: 

Post Date *

Post Title *