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.
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?
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.
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
.
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.
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)
...
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.
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.