Wednesday 20 July 2011

How to Lock and Unlock Users with ASP.NET 4.0 and C#


This tutorial will walk you through the steps of configuring ASP.NET's Login Control to lock users out if they fail to login too many times, and show you how to automatically unlock them in C#.

Creating a User
At this point in the tutorial, I have created a new ASP.NET Empty Web Site in Microsoft Visual Web Developer. The first thing we need to do here is setup an account so that we can test our Login Control.

Enabling User Creation
To enable user creation:
  1. Click the ASP.NET Configuration icon in the Solution Explorer to open up the ASP.NET Website Administration Tool.
  2. In the ASP.NET Website Administration Tool click the Security tab.
  3. Under the Users header click the Select authentication type link.
  4. Select From the internet and click Done.
Creating Our Account
To create a new account:
  1. In the ASP.NET Website Administration Tool click the Security tab.
  2. Under the Users header click the Create Users link.
  3. Create a new user and click Done.
  4. Close the ASP.NET Website Administration Tool.
ASPNETDB.mdf at a Glance
Very briefly I want to go over what actually happened here behind the scenes:
  1. Click the Refresh icon in the Solution Explorer.
  2. SS1.jpg
  3. Notice the new folder App_Data. If you expand it you will see a new database named ASPNETDB.mdf.

We used over 10 web hosting companies before we found Server Intellect. Their dedicated servers and add-ons were setup swiftly, in less than 24 hours. We were able to confirm our order over the phone. They respond to our inquiries within an hour. Server Intellect's customer support and assistance are the best we've ever experienced.

ASPNETDB.mdf is the default database associated with all of ASP.NET's Login Controls. All of the data necessary to handle our user's accounts are located in this database including the account that we created earlier.

Creating a ConnectionString to ASPNETDB.mdf
The next thing that we want to do is create our own ConnectionString to ASPNETDB.mdf. By default everything is already setup so that our website has access to this database including a ConnectionString. However, in order to setup our SqlMembershipProvider we will have to specify a ConnectionString that we have access to, which we will need to define in the Web.Config file. To do this:
  1. Open the Web.Config file for editing.
  2. Between the <configuration> and <system.web> opening tags add the following code:
<!-- Add in our connection string that will point to the ASPNETDB.mdf in App_Data-->
<connectionStrings>
<add name="MyConnection" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\ASPNETDB.mdf;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient"/>
</connectionStrings>

Adding a New SqlMembershipProvider
The SqlMembershipProvider is the class that determines how many password attempts the user is allowed over what time period. By default a SqlMembershipProvider is provided for us that allows 5 password login attempts every 10 minutes. We want to customize these values, so we are going to create a new SqlMembershipProvider in which we can set our own values. To do this:
  1. Open the Web.Config file for editing.
  2. Between the <compilation> tag and the </system.web> closing tag add the following code:
<!-- Set our membership provider to the new SqlMembershipProvider we will add-->
<membership defaultProvider="SqlProvider">
<providers>
<!-- Add the new SqlMembershipProvider-->
<add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="MyConnection" maxInvalidPasswordAttempts="2" passwordAttemptWindow="1" applicationName="/"/>
</providers>
</membership>

Here we are defining the default SqlMembershipProvider for our website to use and configuring the properties appropriately.

name="SqlProvider"
Here we are giving our new SqlMembershipProvider the name "SqlProvider" which should correspond to the defaultProvider property in the membership tag above it. This simply tells our website to use this provider instead of the default one.
type="System.Web.Security.SqlMembershipProvider"
This simply defines the type of provider. In this case we want the SqlMembershipProvider.
connectionStringName="MyConnection"
Here we are defining which ConnectionString to use. I am using the one named "MyConnection" that we added to the top of the Web.Config file earlier which links to the ASPNETDB.mdf database.
maxInvalidPasswordAttempts="2"
Here we define how many invalid password attempts the user will get before they are locked out. For testing purposes I am going to choose a small number, but you would want to set this to something more appropriate for your needs.
passwordAttemptWindow="1"
Here we define the window of time, in minutes, that the user must enter in their invalid password attempts to get locked out. Also, we will use this to determine how long they will be locked out later. For now I chose 1 minute for testing purposes; however you would want to set this value to something more appropriate for your needs.
applicationName="/"
Here we define the application name, in this case I am leaving it default to "/". However, you would want this value to correspond to the application name found in the aspnet_Applications table of the ASPNETDB.mdf database.

Yes, it is possible to find a good web host. Sometimes it takes a while. After trying several, we went with Server Intellect and have been very happy. They are the most professional, customer service friendly and technically knowledgeable host we've found so far.

Getting Locked Out
Next we want to go ahead and test this to make sure that we can Login, which will tell you that your SqlMembershipProvider and ConnectionString are configured properly, and to make sure that we will get locked out after 2 invalid password attempts. We will need to add a Web Form to the website that contains a Login Control so we can test this out. To do this:
  1. Add a new Web Form to the project named 'Default.aspx' and open it up to Design mode.
  2. Expand the Login tab in your toolbox.
  3. Drag and drop a Login Control to the Web Form.
  4. Load up the website.
  5. Login using the account created earlier. If your login was a success, you should not see an error message. (Note: If you are getting an error message here and you are using the correct account information, this indicates that either your ConnectionString or SqlMembershipProvider are not working correctly.)
  6. Attempt to login using the username created earlier, but with an incorrect password. Do this at least twice in one minute to ensure that the account will get locked out.
  7. Attempt to login again using the account created earlier. Notice that even when the correct information is entered, you are not able to login. This means we have successfully been locked out.
If you were to wait a few minutes and try to login again, it would still fail. This is because there is nothing in place to unlock your account by default. For an account to be unlocked, either the UnlockUser() method must be executed on your account in code somewhere, or the IsLocked column of the aspnet_Membership table associated with your user must be set to false manually.

Unlocking Users Automatically
Since we obviously don't want our users locked out forever, and most likely we don't want to manually unlock their accounts every time something goes wrong we need to implement some code to unlock their accounts after a set time period. In this case, I am going to lock the user out for a number of minutes equal to the password attempt window starting immediately when they are locked out. To do this:
  1. Open the Default.aspx page for editing in Design mode.
  2. Right click the Login Control and select Properties.
  3. In the Properties window click the Events icon.
  4. SS2.jpg
  5. Double click the LoggingIn event to begin editing that method.
The method that this brings us to is executed when the user clicks the Login button on the Login Control and before the user is authenticated. Here we will want to check if they are locked out, and if they are see if they have been locked out long enough to be able to login again. To add this logic, first add in the following code at the top of the class with the other using statements:
using System.Web.Security;

We are using Server Intellect and have found that by far, they are the most friendly, responsive, and knowledgeable support team we've ever dealt with!

Next, add the following code to the LoggingIn event method:
//Check to see if the current user exists
if (Membership.GetUser(Login1.UserName) != null)
{
    //Check to see if the user is currently locked out
    if (Membership.GetUser(Login1.UserName).IsLockedOut)
    {
        //Get the last lockout  date from the user
        DateTime lastLockout = Membership.GetUser(Login1.UserName).LastLockoutDate;
        //Calculate the time the user should be unlocked
        DateTime unlockDate = lastLockout.AddMinutes(Membership.PasswordAttemptWindow);

        //Check to see if it is time to unlock the user
        if (DateTime.Now > unlockDate)
            Membership.GetUser(Login1.UserName).UnlockUser();
    }
}

Let's go over this code line by line:

if (Membership.GetUser(Login1.UserName) != null)
This if statement attempts to get the user with the username in the Login Control from the database. If it is not null, the user was found so we need to check if they are currently locked out.
if (Membership.GetUser(Login1.UserName).IsLockedOut)
This if statement checks to see if the user attempting to login is locked out or not. If they are, we need to find out if it is time to unlock their account.
DateTime lastLockout = Membership.GetUser(Login1.UserName).LastLockoutDate;
This will create a DateTime variable and set it equal to the date and time that they were locked out.
DateTime unlockDate = lastLockout.AddMinutes(Membership.PasswordAttemptWindow);
This will calculate the time that the user should be unlocked. Here we take their lockout time and add to it the number of minutes we gave our PasswordAttemptWindow. This amount of time is up to you, but for now I wanted it set to about a minute anyways for testing.
if (DateTime.Now > unlockDate)
This if statement checks to see if the unlock date is prior to the current time; if it is then we need to unlock them.
Membership.GetUser(Login1.UserName).UnlockUser();
Here we simply call the UnlockUser method to unlock their account.

Testing
Now we need to test this again to see if we can login now that enough time has passed for our account to be unlocked. To do this:
  1. Load up the website.
  2. Login using the account created earlier.
  3. Notice that you were now able to login successfully.
To test this again simply get yourself locked out and then wait about a minute before trying to login again.

The Default.aspx source looks like this:
<body>
    <form id="form1" runat="server">
    <div>
    
        <asp:Login ID="Login1" runat="server" onloggingin="Login1_LoggingIn">
        </asp:Login>
    
    </div>
    </form>
</body>

The Default.aspx.cs codebehind looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void Login1_LoggingIn(object sender, LoginCancelEventArgs e)
    {
        //Check to see if the current user exists
        if (Membership.GetUser(Login1.UserName) != null)
        {
            //Check to see if the user is currently locked out
            if (Membership.GetUser(Login1.UserName).IsLockedOut)
            {
                //Get the last lockout  date from the user
                DateTime lastLockout = Membership.GetUser(Login1.UserName).LastLockoutDate;
                //Calculate the time the user should be unlocked
                DateTime unlockDate = lastLockout.AddMinutes(Membership.PasswordAttemptWindow);

                //Check to see if it is time to unlock the user
                if (DateTime.Now > unlockDate)
                    Membership.GetUser(Login1.UserName).UnlockUser();
            }
        }
    }
}

The Web.Config looks like this:
<configuration>
    <!-- Add in our connection string that will point to the ASPNETDB.mdf in App_Data-->
    <connectionStrings>
    <add name="MyConnection" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\ASPNETDB.mdf;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient"/>
    </connectionStrings>
<system.web>
<authentication mode="Forms"/>
<compilation debug="true" targetFramework="4.0"/>
        <!-- Set our membership provider to the new SqlMembershipProvider we will add-->
        <membership defaultProvider="SqlProvider">
        <providers>
        <!-- Add the new SqlMembershipProvider-->
        <add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="MyConnection" maxInvalidPasswordAttempts="2" passwordAttemptWindow="1" applicationName="/"/>
        </providers>
        </membership>
</system.web>
</configuration>

No comments:

Post a Comment