Overview

I'm making a concerted effort to give myself a more understanding of ASP.Net.  One of the things I recall from classic ASP and my early forays in to ASP.Net was creating authentication for site.  Generally we created a standard set of functions that we could reuse, but the generally always ended up being 'tweaked' from project to project.  Along with ASP.Net 2.0 Microsoft delivered provider models.  These models can be used for site membership and roles and so looking at the membership role immediately came to mind as being something interesting to look at.

Implementation

I'm just going to create a simple ASP.Net web application and add some authentication to it.  In essence, this is a simple as dropping on a Login and a Create User Wizard web user control on to a page or two in to the project. This will automatically create a SQL Express database to store our user information.  All of the Login controls will pretty much work using this approach.  Me being me however, I don't want to use the MS SQL Express database as it is.  Yes, I will be using SQL Express, but I want to use my tables and store the information how I want.  To the end, I need to write my own membership provider to provide the kind of information that I need to e stored and retrieved.

So, the fist thing I do is add a class to my web project called MySqlMembershipProvider in the App_Code folder.  From reading various posts around the interned, all I need to do is make this class inherit from the System.Web.Security.MembershipProvider class and all of the stubbed out derived methods will be created for me.  Nah, not really - I'm using C# so I have to right click on the inherited class and select 'Implement Abstract Class' to tell it to go do this for me.  VB.Net (*spit) does do this automatically however if you're using that.

I'm going to implement two methods on this test application:

  • CreateUser(): This will allow me to add new users to the database.
  • ValidateUser(): Given a couple of details (namely user name and password) this will tell me if the user exists or not.

These are enough to be able to log on to our site and create new users and that is all that I want to do for the moment.

So, first of all, we'll create a database.  I'm using a SQL Express, but you really can use anything you like here; MySQL, an XML file or perhaps a link to Active Directory.  The choice is yours, but the SQL example is easier because pretty much everyone gets it.  In my database I'm going to create a table called 'User' as follows in my database:

Name Type Key
Username NVarChar(50) Primary Key
Password NVarChar(50) No
EmailAddress NVarChar(50) No
PasswordQuestion NVarChar(50) No
PasswordAnswer NVarChar(50) No

 

Once I've created this, I can begin development.  We'll carry on with the MySqlMembershipProvider class.

I'm going to override the Initialize class and add some functionality to read some settings from the web.config file.  I'm going to store one property:

  • connectionString will store the connection string to my database.  In my case, this connection string is as follows:
    Data Source=.\SQLEXPRESS;AttachDbFilename=F:\SQLMembershipProvider\App_Data\SQLMemberShipProvider.mdf;Integrated Security=True;User Instance=True

Create this as private variable in the class to store this and then to the MySqlMembershipProvider add the following code:

/// <summary>
/// Set up the initial properties that we need when the provider is first loaded
/// </summary>
/// <param name="name">The full name of the provider.</param>
/// <param name="config">Contents of the web.config for the provider</param>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
    connectionString = config["connectionString"];
    base.Initialize(name, config);
}

The above code loads the connection string so that we can use it later.  Not that Initialize is not created when we implemented that abstract class, it's one that we need to do ourselves.

As I touched on earlier, we then need to implement 'CreateUser' and 'ValidateUser' so that we will be able to logon and create new users on the site.  The two methods used for this have already been created so we will need to replace the line in the stubbed method with some real code to insert to the database and also retrieve users form the database.  So, in my example, modify those methods to look as follows:

/// <summary>
/// Creates a user in the provider database and returne an instance of that user.
/// </summary>
/// <param name="username">Provided username</param>
/// <param name="password">Provided password</param>
/// <param name="email">Provided email address</param>
/// <param name="passwordQuestion">Provided password question</param>
/// <param name="passwordAnswer">Provided password answer</param>
/// <param name="isApproved">Boolean indicateing if the user is allowed to logon.</param>
/// <param name="providerUserKey">The user identifier for the user that should be stored in the membership database</param>
/// <param name="status">The status indicated success of user created or reason for failure.</param>
/// <returns>A MembershipUser object for the newly created user</returns>
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
    SqlConnection sqlConnection = new SqlConnection(this.connectionString);
    try
    {
        sqlConnection.Open();
        SqlCommand sqlCommand = new SqlCommand();
        sqlCommand.Connection = sqlConnection;
        sqlCommand.CommandText = "INSERT INTO User VALUES (@username, @password, @email, @passwordQuestion, @passwordAnswer )";
        sqlCommand.CommandType = CommandType.Text;
        sqlCommand.Parameters.AddWithValue("@username", username);
        sqlCommand.Parameters.AddWithValue("@password", password);
        sqlCommand.Parameters.AddWithValue("@email", email);
        sqlCommand.Parameters.AddWithValue("@passwordQuestion", passwordQuestion);
        sqlCommand.Parameters.AddWithValue("@passwordAnswer", passwordAnswer);

        int result = sqlCommand.ExecuteNonQuery();

        sqlCommand = null;
        sqlConnection.Close();

        status = MembershipCreateStatus.Success;
        return new MembershipUser(
            "SQLMembershipProvider.MySQLMembershipProvider",
            username,
            null,
            email,
            passwordQuestion,
            string.Empty,
            true,
            false,
            DateTime.Now,
            DateTime.MinValue,
            DateTime.MinValue,
            DateTime.MinValue,
            DateTime.MinValue
            );
    }
    catch (Exception ex)
    {
        System.Diagnostics.Trace.Write("CreateUserFailed: " + ex.Message);
        // HACK: I'm just going to say we failed here.  We can however be more creative and say _why_
        status = MembershipCreateStatus.UserRejected;
        return null;               
    }
}

/// <summary>
/// Check to see if the username and password combination exists in the user table.
/// </summary>
/// <param name="username">Supplied Username</param>
/// <param name="password">Supplied Password</param>
/// <returns>Boolean indicating match success</returns>
public override bool ValidateUser(string username, string password)
{
    // Connect to the providers DB
    SqlConnection sqlConnection = new SqlConnection(this.connectionString);
    try
    {
        sqlConnection.Open();
        SqlCommand sqlCommand = new SqlCommand();
        sqlCommand.Connection = sqlConnection;
        // I spit at myself for hard-coded SQL, but this is only a demo :)
        sqlCommand.CommandText = "Select Username, Password, Email, PasswordQuestion, PasswordAnswer From User WHERE username=@username AND password=@password";
        sqlCommand.CommandType = CommandType.Text;

        sqlCommand.Parameters.AddWithValue("@username", username);
        sqlCommand.Parameters.AddWithValue("@password", password);

        SqlDataReader reader = sqlCommand.ExecuteReader();

        // If there are rows, then the login succeeds.  Due to the primary key, there should only ever be a maximum of one row anyway
        return reader.HasRows;

        sqlConnection.Close();
    }
    catch 
    {
        // HACK: Should really log any technical errors rather than just ignoring them in this way
        return false;
    }
}

This is almost everything fro this class, but I do want the user to be able to enter a secrete password question and answer,  There is a public property for this that at the moment throws and exception as we have not implemented it.  SO we'll just make this return true so that the web controls know what to do for now.  WE can put this in to the web.config file, but for this example I'm not going to.  Just modify the RequiresQuestionAndAnswer to return true.

We need to modify the web.config file to tell it which MembershipProvider to use.  So, in the web.config, change the authentication mode to 'Forms' and add the membership section as demonstrated below:

<authentication mode="Forms" />
<membership defaultProvider="SQLMembershipProvider.MySQLMembershipProvider" >
    <providers>
        <add name="SQLMembershipProvider.MySQLMembershipProvider"
pe="SQLMembershipProvider.MySQLMembershipProvider"
nnectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=F:\SQLMembershipProvider\App_Data\SQLMemberShipProvider.mdf;Integrated Security=True;User Instance=True" />
    </providers>
</membership>

Right, that is the trick bit.  Next we simply need to create two pages in out web application; one page to login and one page to be the homepage.  We'll call these (wait for it) Default.aspx and Login.aspx.

So, to start with we'll create Login.aspx.  In design mode, drop a Login control on to the page.  Right, that's that page done.

Now lets create Default.aspx.  Again, switch to design view so we can drag some controls on.  The first two controls are the Create User Wizard and the Login View. 

Drag a CreateUserWizard control on to the page.  We need to change one property of this control, and the is ContinueDestinationPageUrl.  Change this property to Login.aspx so that when a user is created they will then be asked to login.

Next, drag a LoginView control on to the page.  This control allows us to see different things depending on who we are.  In the tasks tool-tip, select the AnonymousTemplate view.  In to the LoginView, drag a LoginStatus control.  This gives a simple login button for unauthenticated users.

Next, switch to the LoggedInTemplate view of the LoginView control. In here we will also add a LoginStatus control.  We will also however add a LoginName control so that we can tell logged in users who they are.  Type some guff around thsi so that you end up with something like the following in your LoginView:

You are logged in as [UserName].

That pretty much should be it.  You should just be able to hit F5 and run the project.


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist
posted @ Thursday, April 10, 2008 9:09 AM | in ASP.Net .Net Framework Visual Studio

Comments

No comments posted yet.

Post Comment

Title *
Name *
Email
Url
Comment *  


Please add 3 and 1 and type the answer here: