Logging Users into an Application

One of the primary reasons that we use the state information is to identify each user. They are already identified within ASP by a session ID, but this tends not to be very useful. What we will generally want to do is change the behavior of our application based on the users absolute identity, i.e. a UserID or nickname. This follows a similar practice to a normal, non Web-based client/server application.

It's possible to identify users through advanced communication methods, when using Secure Channels or Challenge/Response Authentication—subjects that are covered in Chapter 9. However, often we'll need to implement a standard log-on type of dialog, and then use the information the user provides within our application.

The user supplies a user name and a password, and clicks the Submit button. All we need to do is respond to the request by checking these details. It makes sense to use the POST method for the form so that the information is not send attached to the URL in the query string, but at least partly concealed within the HTTP header. Of course we should really use some type of secure transmission as well, to ensure maximum privacy.

Using the User's Information

However, having got the request at the server, we can examine the values the user entered:

txtUserName = Request.Form("txtUserName")
txtPassWord = Request.Form("txtPassWord")

The question now is what do we do with it? We can use it in our code in minimalist ways, like:

Welcome <% = txtUserName %> to our site

and even to redirect users, or control access to the pages:

If txtUserName = "Admin" And txtPassWord = "secret" Then
   Response.Redirect "AdminPage.asp"
ElseIf txtUserName = "Manager" And txtPassWord = "reports" Then
   Response.Redirect "SalesReports.asp"
Else
   Response.Redirect "UserMenu.asp"
End If

However, this doesn’t really provide a foundation for 'proper' application design. We need to be able to log users in, verify their passwords against a central list, and decide what further action to take. For example, we might want to deny access to users who don't have an existing user name (i.e. not accept new users), or we might want to automatically add them to our user list and give them certain default access permission. And if they have been here before, do we have any user preferences we need to set, like the background and foreground colors of their pages?

Verifying the User

Generally, the first step will be to verify who the user is, and decide if we already know them. Then, if we do know them, we can check to see if they've supplied the correct password. This process involves three basic steps, and we'll normally be taking the information from a database of some kind. In the following example we'll work through the steps, which are:

See if the user name exists in the database.

If it does, check that the password is correct.

If it doesn't, add the user to the database if appropriate.

Does the User Already Exist?

To see if the user already exists in our database, we just need to search for their user name in the appropriate table. Assuming we have a table UserDetails with UserName and UserPword fields, we can use an SQL query to extract the details. However, we may need to ensure that the length of the database fields and the validation are identical, depending on how the database actually stores text fields.

The database system may offer two types of text field—varChar and char. If the field is of type char and length 10, we would need to pad out the string to 10 characters first, to ensure that we get a match. Using varchar, or a normal Text field in Access, avoids this problem.

So, we can use the normal ADO object methods in our page to find out if the user exists. Here, we're using a connection string that we previously stored in the Session object, so that we can retrieve it as required:

'create SQL string using the value in the txtUserName control on the form
strSQL = "SELECT UserName, UserPword FROM UserDetails WHERE UserName = '" _
       & Request.Form("txtUserName") & "')"
'create the database connection and open a recordset with the results
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open Session("Logon_ConnectionString"), 
Set oUsers = Server.CreateObject("ADODB.Recordset")
oUserRs = oConn.Execute(strSQL) 
'now check if there is a record
If oUserRs.BOF AND oUserRs.EOF Then           'we didn't get any records
   Response.Write("User name does not exist")
   ...

Once we've got the recordset, we just need to see if there was a record in it—there will only be one at most, because the user name would need to be unique, and defined this way in the database tables. We might try using the RecordCount property, but this requires a move to the last record first (see Chapter 4 for more about how this works). And if we do this with an empty recordset, it causes an error anyway. The easier way is to do what we do here, and check the values of BOF and EOF. Only if the recordset is empty will they both be True.

Does the Password Check Out?

If this first check discovers a record, then the user exists in the database, so we can now compare the password. The recordset we retrieved contains the password field as well, so it's just a matter of a straight comparison:

...
Else
   If Request.Form("txtPassword") <> oUserRs.Fields("UserPword") Then
      Response.Write("You did not enter the correct password")
      ...

The usual option here would be to allow them another attempt, by displaying the login form again. It's quite possible to do this in the same page, and in the next few chapters you'll see how we can achieve this.

Adding New Users to the Database

If the user name check fails, in other words the user doesn’t already exist, we need to decide if we want to add them to the database as a new user. Again, an SQL statement can do this. We know that the username they have supplied is unique compared to the existing ones, because we didn’t get a record returned. All we need is an appropriate SQL INSERT statement:

...
'add the user to the database
strSQL = "INSERT INTO UserDetails (UserName, UserPword) VALUES ('" _
       & Request.Form("txtUsername") & "','" _
       & Request.Form("txtPassword") & "')"
oConn.Execute(strSQL)
...

Ensuring Concurrency During Logon

There is just one problem. If we are going to add records to a globally accessible database, we need to make sure that we don’t upset any other logons that are being performed concurrently. For small sites this risk is marginal, but consider what would happen if two new users specified the same user name. The recordset we retrieved would be out of date by the time we came to add the new record, and the original could by now already contain the user name we are trying to add.

We could get round this in several ways, such as by making sure the database table design specified unique values—and then trapping the error that would arise. However, this approach is not ASP-centric, so instead we'll try one that makes use of the features available in ASP. What we need is a concurrency model that controls access to an item during the process, preventing two sessions accessing it at the same time.

Locking the Connection

Here's one possibility. Having decided to add the user, we do another search of the database for this user name in case it has been added meanwhile, by another visitor. However, we first lock the Application object while we read the connection string, and keep it locked until we've finished the whole process. Now, no other session can create a connection, and upset our code. Notice also that we only need to extract the user name and not the password as well. And the result we want is to not find a record this time

'create SQL string using the value in the txtUserName control on the form
strSQL = "SELECT UserName FROM UserDetails WHERE UserName = '" _
       & Request.Form("txtUserName") & "')"
'create the database connection and open a recordset with the results
Application.Lock
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open Session("Logon_ConnectionString"), 
Set oUsers = Server.CreateObject("ADODB.Recordset")
oUserRs = oConn.Execute(strSQL) 
'now check if there is a record
If oUserRs.BOF AND oUserRs.EOF Then      'we didn't get any records
   strSQL = "INSERT INTO UserDetails (UserName, UserPword) VALUES ('" _
          & Request.Form("txtUsername") & "','" _
          & Request.Form("txtPassword") & "')"
   oConn.Execute(strSQL)
Else
   Response.Write("Error accessing database, please try again")
End If
Application.Unlock

We could, of course, have just locked the Application originally, and this would have saved the second search through. It all depends on whether you expect to get more existing users than new visitors, or vice-versa.

© 1997 by Wrox Press. All rights reserved.