Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Classic ASP Login System Utilizing a Token

Scott FellDeveloper & Coffee Roaster
CERTIFIED EXPERT
I have over 30 years experience in Sales, Marketing, Technology and Developing solutions.
Published:
Updated:

This demonstration started out as a follow up to some recently posted questions on the subject of logging in: http://www.experts-exchange.com/Programming/Languages/Scripting/JavaScript/Q_28634665.html and http://www.experts-exchange.com/Programming/Languages/Scripting/ASP/Q_28641013.html. The Asker was originally trying to find a way to eliminate the scenario where his users are staring at the screen thinking and writing only to click the submit button and find out that they were logged out and their updates are lost.


I know many people may try adjusting the script timeout or the session timeout only to find this does not work as expected. The reason is they forget about the idle timeout that is set to the default of 5 minutes in IIS: https://technet.microsoft.com/en-us/library/cc771956%28v=ws.10%29.aspx


Simply adjusting the idle timeout to something longer coupled with adjusting your session.timeout=60 (for 60 minutes) is not the best solution. Session variables can be reset if the app pool recycles or crashes: http://weblogs.asp.net/owscott/why-is-the-iis-default-app-pool-recycle-set-to-1740-minutes


An alternative is to issue a new and unique token at each log in. Then set a cookie using the generated token as well as storing the token in a user table in your database. On each page load, look up the cookie with the token, and look for a match in the user login table. If a match is found, test if the expiration date is valid. When everything checks out, give access to the page.


I have created a more detailed sample using this process that also allows for user levels. This demonstration is intended to show the process and is not meant to be a production-ready login system.


I am using MSSQL SERVER 2012 with two tables and one view. The first table is a users table where I am storing the user's name, email, hashed password and user level.


The second table is the login transaction table where I store the tokens with a user id, the token expiration, the IP used to log in and and the timestamp of the log in. I have the field LoggedTimeStamp set to the current date/time using getdate().


The view of logged in users selects rows of data from the Log In Trans table where the TokenExpires is greater than the current timestamp and the token field is not blank.

CREATE TABLE [dbo].[ee_tUsers](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [FirstName] [nvarchar](50) NULL,
    [LastName] [nvarchar](50) NULL,
    [Email] [nvarchar](150) NULL,
    [UserName] [nvarchar](50) NULL,
    [Password] [nvarchar](350) NULL,
    [UserLevel] [nvarchar](50) NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[ee_tLoginTrans](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [UserID] [int] NULL,
    [UserName] [nvarchar](50) NULL,
    [Token] [nvarchar](350) NULL,
    [TokenExpires] [datetime] NULL,
    [LoggedIP] [nvarchar](50) NULL,
    [LoggedTimeStamp] [datetime] NULL,
) ON [PRIMARY]


CREATE VIEW [dbo].[ee_vLoggedInUsers]
AS
SELECT 
    dbo.ee_tLoginTrans.UserID, dbo.ee_tLoginTrans.UserName, dbo.ee_tLoginTrans.Token,         dbo.ee_tLoginTrans.TokenExpires, dbo.ee_tLoginTrans.LoggedIP, dbo.ee_tLoginTrans.LoggedTimeStamp, dbo.ee_tUsers.UserLevel

FROM 
    dbo.ee_tUsers RIGHT OUTER JOIN
    dbo.ee_tLoginTrans ON dbo.ee_tUsers.ID = dbo.ee_tLoginTrans.UserID

WHERE
    (dbo.ee_tLoginTrans.TokenExpires > GETDATE()) AND (dbo.ee_tLoginTrans.Token <> N'')
    



A side benefit of using a log in transaction table like this is you can run a report of who logged in with the time stamp and IP.  You can also view the current list of logged in users and if you like, remove their token and thus logging them out.


The three flow charts below shows the process of logging in, creating the token and testing authentication on each page.


Login.jpg


Login2.jpg


Login3.jpgThe file structure I am using in this demo uses an includes folder where I have a config.asp, functions.asp and nav.asp.


The config.asp is used to store my database connection and other variables such as the amount of time to be logged in and a secret key used in the hashing functions.


The Functions.asp is used to store functions I will use on multiple pages such as sha256, setting passwords, setting the token, looking up the token.


The nav.asp are the navigation links.


The first step is to create the two tables and view. Make sure the LoggedTimeStamp field in the ee_tLoginTrans table will default to the current date/time.


You will have to make some adjustments in your code. For my own testing, I have placed the files in a sub folder of one of my domains and you may see code to include a file like . It is important you adjust the path to where you actually store the files. If your includes folder is at the top level of the site, then change to . Note that I could have simply used but purposely didn't. In order to use that method, you would need to have parent paths turned on in IIS and that is a security risk. I see a lot of videos and tutorials demonstrating how to turn parent paths off because it is easier to migrate your old code, but don't do this.


Next, add the pages that will go on the main level


INDEX.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->
<%
if request.form("username")<>"" then
dim cmd,rs
	Set cmd = Server.CreateObject ("ADODB.Command")
	cmd.ActiveConnection = conEE
	cmd.CommandText = "SELECT ID, UserName,UserLevel, Password FROM dbo.ee_tUsers WHERE Username = ?" 
	cmd.Parameters.Append cmd.CreateParameter("username", 202, 1, 350, request.form("username")) 'string
	cmd.Prepared = true
	Set rs = cmd.Execute
		
		if not rs.eof then
			if passwordHash(request.form("username"),request.form("password"))=rs("password") then 'the hash's match
		
			
				setToken rs("ID"),rs("UserName") ' function to set username
				redirectLoginLevel rs("UserLevel") ' function to redirect
				
			end if
		end if

	rs.Close()
	Set rs = Nothing
	
end if
%>
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="UTF-8">
<title>Log In</title>
</head>

<body>

<div id="login">
<h1>Log In</h1>
<form method="post" action="" autocomplete="off">
	<input name="username" placeholder="Username">
    <input name="password" type="password" placeholder="Password">
    <button type="submit">Submit</button>
</form>
</div>
</body>
</html>


ADMIN.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->
<%
' ---------- test for log in and level ------------
if logged_in_status = "0" OR logged_in_level<>"Admin" then
	response.Redirect("index.asp")
end if
%>
<%
dim cmd,arrUsers,UserID,FirstName,LastName,Email,UserName,UserLevel,rs,i
Set cmd = Server.CreateObject ("ADODB.Command")
cmd.ActiveConnection = conEE
cmd.CommandText = "SELECT ID, FirstName,LastName,Email, UserName,UserLevel FROM dbo.ee_tUsers" 
cmd.Prepared = true
Set rs = cmd.Execute

if not rs.eof then
	arrUsers = rs.getrows()
end if

rs.Close()
Set rs = Nothing

%>

<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="UTF-8">
<title>Admin</title>
</head>

<body>
<!--#include virtual="/ee/login/includes/nav.asp" -->
<h1>Admin</h1>
<h2>User List</h2>
<%
If IsArray(arrUsers) Then
	response.write "<table class='usertable'>"
	response.write "<tr><th>Edit</th><th>First</th><th>Last</th><th>Level</th></tr>"
    	For i = LBound(arrUsers, 2) To UBound(arrUsers, 2)
        	UserID 			= arrUsers(0, i) ' order based on sql statment,  ID, FirstName,LastName,Email, UserName,UserLevel 
			FirstName 	= arrUsers(1, i)
       	 	LastName 	= arrUsers(2, i)
			Email			= arrUsers(3, i)
			UserName	= arrUsers(4, i)
			UserLevel 	= arrUsers(5, i)
	
			response.write "<tr><td><a href='userEdit.asp?userid="&UserID&"'>Edit</a></td><td>"&FirstName&"</td><td>"&LastName&"</td><td>"&UserLevel&" </td></tr>"		
			
    	Next
		response.write "</table>"
	End If 
%>

</body>
</html>


LOGGEDINUSERS.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->
<%
' ---------- test for log in and level ------------
if logged_in_status = "0" OR logged_in_level<>"Admin" then
	response.Redirect("index.asp")
end if
%>
<%
dim cmd,arrUsers,UserID,UserName,IP,LogInTime,TokenExpires,UserLevel,rs,i,logoutform

if request.form("logoutuser")<>"" then
	if isnumeric(request.form("logoutuser")) then
		logOutToken(request.form("logoutuser"))
	end if
end if

Set cmd = Server.CreateObject ("ADODB.Command")
cmd.ActiveConnection = conEE
cmd.CommandText = "SELECT UserID, UserName, Token, TokenExpires, LoggedIP, LoggedTimeStamp, UserLevel FROM ee_vLoggedInUsers" 
cmd.Prepared = true
Set rs = cmd.Execute

if not rs.eof then
	arrUsers = rs.getrows()
end if

rs.Close()
Set rs = Nothing

%>

<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="UTF-8">
<title>Admin</title>
</head>

<body>
<!--#include virtual="/ee/login/includes/nav.asp" -->
<h1>Admin</h1>
<h2>User List</h2>
<%
If IsArray(arrUsers) Then
	response.write "<table class='usertable'>"
	response.write "<tr><th>Log Out</th><th>User</th><th>Expires</th><th>IP</th><th>TimeStamp</th><th>Level</th></tr>"
    	For i = LBound(arrUsers, 2) To UBound(arrUsers, 2)
        	UserID 			= arrUsers(0, i) ' order based on sql statment,
			UserName 		= arrUsers(1, i)
       	 	Token 				= arrUsers(2, i)
			TokenExpires	= arrUsers(3, i)
			IP						= arrUsers(4, i)
			LogInTime 		= arrUsers(5, i)
			UserLevel			= arrUsers(6, i)
	
			logoutform= "<form method='post' action=''><input type='hidden' name='logoutuser' value='"&UserID&"'><button type='submit'>Submit</button></form>"
	
			response.write "<tr><td>"&logoutform&"</td><td>"&UserName&"</td><td>"&TokenExpires&"</td><td>"&IP&" </td><td>"&LogInTime&"</td><td>"&UserLevel&" </td></tr>"		
			
    	Next
		response.write "</table>"
	End If 
%>

</body>
</html>


SALES.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->
<%
' ---------- test for log in and level ------------
if logged_in_status = "0" OR instr("Admin Sales",logged_in_level)<1 then
	response.Redirect("index.asp")
end if
%>

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Sales</title>
</head>

<body>
<!--#include virtual="/ee/login/includes/nav.asp" -->
<h1>Sales </h1>
</body>
</html>


SUPPORT.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->
<%
' ---------- test for log in and level ------------
if logged_in_status = "0" OR  instr("Support Admin",logged_in_level)<1 then
	response.Redirect("index.asp")
end if
%>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Support</title>
</head>

<body>
<!--#include virtual="/ee/login/includes/nav.asp" -->
<h1>Support</h1>
<div>This section can be seen by Support and Admin</div>
<%
if logged_in_level = "Admin" then
%>
<div>This section can be seen ONLY by Admin</div>
<%
end if
%>
</body>
</html>


USERADD.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->

<%
' ---------- test for log in and level ------------
if logged_in_status = "0" OR logged_in_level<>"Admin" then
	response.Redirect("index.asp")
end if
%>
<%
Dim rs,cmd, userid,cmdNewPassword




' ---------- CREATE CONTACT -------------
if request.form("action")="createContact" then
	dim update_response, validation, validation_username,validation_password, cmdFirstName, cmdLastName, cmdEmail, cmdUsername, cmdLevel


		validation=0 	' set to not validate
		validation_username=1  'set to validate (in case of blank)
		validation_password = 1  'set to validate (in case of blank)
		
		cmdFirstName = request.form("firstname")
		cmdLastName = request.form("lastname")
		cmdEmail = request.form("email")
		cmdUsername = request.form("username")
		cmdLevel = request.form("userlevel")
		
		'------- validation --------------
		if len(cmdFirstName)<3 then validation=validation+1 end if ' first name should be at least 3 characters
		if len(cmdLastName)<3 then validation=validation+1 end if ' last name should be at least 3 characters
		if len(cmdEmail)<7 then validation=validation+1 end if ' email should be at least 7
		if instr(cmdEmail,"@")<1 then validation=validation+1 end if ' email should contain @ or use RegEX
		if len(cmdLevel)<3 then validation=validation+1 end if ' level should be at least 3 characters
		
		' check if username is unique
		if cmdUsername<>"" then ' allow blank usernames
			Set cmd = Server.CreateObject ("ADODB.Command")
			cmd.ActiveConnection = conEE
			cmd.CommandText = "SELECT ID FROM dbo.ee_tUsers WHERE UserName = ?" 
			cmd.Parameters.Append cmd.CreateParameter("username", 202, 1, 50, cmdUsername) ' adDouble
			cmd.Prepared = true
			Set rs = cmd.Execute
			
			if not rs.eof then ' the username is in use, don't update
				validation_username=0
			end if
		
			rs.Close()
			Set rs = Nothing
		end if
		
		' validate password
		cmdNewPassword = ""
		if request.form("newpassword")<>"" then ' try and add new password
			if validatePassword(request.form("newpassword"))= True then
				cmdNewPassword=request.form("newpassword")
				cmdNewPassword= passwordHash(cmdUsername,cmdNewPassword)
				else
				validation_password=1
			end if
		end if
		' -------- end validation ----------
		if validation = 0 AND validation_username = 1 AND validation_password = 1   then ' if no errros proceed
		
			Set cmd = Server.CreateObject ("ADODB.Command")
			cmd.ActiveConnection = conEE
			cmd.CommandText = "INSERT INTO dbo.ee_tUsers ( FirstName, LastName,Email, UserLevel,Username, Password) VALUES (?,?,?,?,?,?)" 
			cmd.Prepared = true
			cmd.Parameters.Append cmd.CreateParameter("first", 202, 1, 50, cmdFirstName) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("last", 202, 1, 50, cmdLastName) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("email", 202, 1, 150, cmdEmail) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("level", 202, 1, 50, cmdLevel) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("username", 202, 1, 50, cmdUsername) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("password", 202, 1, 350, cmdNewPassword) ' adVarWChar
			cmd.Execute
			cmd.ActiveConnection.Close
			
			update_response="success|User Updated"
			
			' check if username should be updated
		
			
			
			
			else
			
			update_response="error|You have "&validation&" errors.  Please set all fields"
			if validation_password = 0 then
				update_response=update_response&"<br>Invalid Password"
			end if
			if validation_username = 0 then
				update_response=update_response&"<br>Username in use"
			end if
		end if

	' reset
	cmdFirstName="" 
	cmdLastName=""
	cmdEmail=""
	cmdUsername=""
	cmdLevel=""

end if

%>

<%
 'setPassword "password",1
%>
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="UTF-8">
<title>Contact Edit</title>
</head>

<body>
<!--#include virtual="/ee/login/includes/nav.asp" -->
<%

dim arrMessage,messageClass,messageContent
' if there is a form update, show mesage
	if update_response<>"" then ' this is delimted by a pipe class|message
		arrMessage = split(update_response,"|")
		messageClass = arrMessage(0)
		messageContent = arrMessage(1)
		response.write "<div class='"&messageClass&"'>"&messageContent&"</div>"
		'response.write messageContent
	end if
%>
<form method="post" action=""  autocomplete="off">
<input type="hidden" name="action" value="createContact">
<input type="hidden" name="contactid">
<label>First Name</label>
<input name="firstname" >
<label>Last Name</label>
<input name="lastname" >
<label>Email</label>
<input name="email">
<label>username</label>
<input name="username"  >
<label>User Level</label>
<select name="userlevel">
	<option value="">No Log In</option>
    <option value="Admin">Admin</option>
    <option value="Support">Support</option>
    <option value="Sales">Sales</option>
</select>

<p class="notice">Leave blank for no password</p>
<label>New Password</label>
<input name="newpassword">
<button type="submit">Submit</button>
</form>

</body>
</html>


USEREDIT.ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!--#include virtual="/ee/login/includes/config.asp" -->
<!--#include virtual="/ee/login/includes/functions.asp" -->

<%
' ---------- test for log in and level ------------
if logged_in_status = "0" OR logged_in_level<>"Admin" then
	response.Redirect("index.asp")
end if
%>
<%
Dim rs,cmd, userid,cmdPassword

' ---------- SET USER ID FROM QUERYSTRING -------------
userid=0
if request.QueryString("userid")<>"" then
	if isnumeric(request.QueryString("userid")) then
	 	userid = request.QueryString("userid") 
	end if
end if

' ---------- UPDATE CONTACT -------------
if request.form("action")="updateContact" then
	dim update_response, validation, validation_username, cmdFirstName, cmdLastName, cmdEmail, cmdUsername, cmdLevel


		validation=0
		validation_username=1
		
		cmdFirstName = request.form("firstname")
		cmdLastName = request.form("lastname")
		cmdEmail = request.form("email")
		cmdUsername = request.form("username")
		cmdLevel = request.form("userlevel")
		
		'------- validation --------------
		if len(cmdFirstName)<3 then validation=validation+1 end if ' first name should be at least 3 characters
		if len(cmdLastName)<3 then validation=validation+1 end if ' last name should be at least 3 characters
		if len(cmdEmail)<7 then validation=validation+1 end if ' email should be at least 7
		if instr(cmdEmail,"@")<1 then validation=validation+1 end if ' email should contain @ or use RegEX
		if len(cmdLevel)<3 then validation=validation+1 end if ' level should be at least 3 characters
		
		' check if username is unique
		if cmdUsername<>"" then ' allow blank usernames
			Set cmd = Server.CreateObject ("ADODB.Command")
			cmd.ActiveConnection = conEE
			cmd.CommandText = "SELECT ID FROM dbo.ee_tUsers WHERE UserName = ? AND ID<>?" 
			cmd.Parameters.Append cmd.CreateParameter("username", 202, 1, 50, cmdUsername) ' adDouble
			cmd.Parameters.Append cmd.CreateParameter("userid", 5, 1, -1, userid) ' adDouble
			cmd.Prepared = true
			Set rs = cmd.Execute
			
			if not rs.eof then ' the username is in use, don't update
				validation_username=0
			end if
		
			rs.Close()
			Set rs = Nothing
		end if
		' -------- end validation ----------
		if validation = 0 then ' if no errros proceed
		
			Set cmd = Server.CreateObject ("ADODB.Command")
			cmd.ActiveConnection = conEE
			cmd.CommandText = "UPDATE dbo.ee_tUsers SET FirstName = ?, LastName =?, Email =?, UserLevel=? WHERE ID = ?" 
			cmd.Prepared = true
			cmd.Parameters.Append cmd.CreateParameter("first", 202, 1, 50, cmdFirstName) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("last", 202, 1, 50, cmdLastName) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("email", 202, 1, 150, cmdEmail) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("level", 202, 1, 50, cmdLevel) ' adVarWChar
			cmd.Parameters.Append cmd.CreateParameter("userid", 5, 1, -1, userid) ' adDouble
			cmd.Execute
			cmd.ActiveConnection.Close
			
			update_response="success|User Updated"
			
			' check if username should be updated
			if validation_username=1 then
				Set cmd = Server.CreateObject ("ADODB.Command")
				cmd.ActiveConnection = conEE
				cmd.CommandText = "UPDATE dbo.ee_tUsers SET Username = ? WHERE ID = ?" 
				cmd.Prepared = true
				cmd.Parameters.Append cmd.CreateParameter("username", 202, 1, 50, cmdUsername) ' adVarWChar
				cmd.Parameters.Append cmd.CreateParameter("userid", 5, 1, -1, userid) ' adDouble
				cmd.Execute
				cmd.ActiveConnection.Close
				
				else
				
				update_response=update_response&"<br><div class='error'>Username in use, not updated</div>"
				
			end if
			
			' check if password should be updated
			if request.form("newpassword")<>"" then 'password was submitted
			
				cmdPassword = request.form("newpassword") 
				
				if validatePassword(cmdPassword)=True then ' password is valid
					if setPassword(cmdPassword,userid) = 1 then ' run function to update password and it is good
						update_response=update_response&"<br>Password Updated"
					end if
					
				else
					update_response=update_response&"<br><div class='error'>Password did not validate and was not updated</div>"
				end if
				
			end if
			
			else
			
			update_response="error|You have "&validation&" errors.  Please set all fields"
		end if

	' reset
	cmdFirstName="" 
	cmdLastName=""
	cmdEmail=""
	cmdUsername=""
	cmdLevel=""

end if

' ---------- CONTACT RECORDSET -------------
Set cmd = Server.CreateObject ("ADODB.Command")
cmd.ActiveConnection = conEE
cmd.CommandText = "SELECT ID, FirstName,LastName,Email, UserName,UserLevel FROM dbo.ee_tUsers WHERE ID = ?" 
cmd.Parameters.Append cmd.CreateParameter("userid", 5, 1, -1, userid) ' adDouble
cmd.Prepared = true
Set rs = cmd.Execute
%>

<%
 'setPassword "password",1
%>
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="UTF-8">
<title>Contact Edit</title>
</head>

<body>
<!--#include virtual="/ee/login/includes/nav.asp" -->
<%
if not rs.eof or not rs.bof then ' only show form if good recordset
dim arrMessage,messageClass,messageContent
' if there is a form update, show mesage
	if update_response<>"" then ' this is delimted by a pipe class|message
		arrMessage = split(update_response,"|")
		messageClass = arrMessage(0)
		messageContent = arrMessage(1)
		response.write "<div class='"&messageClass&"'>"&messageContent&"</div>"
		'response.write messageContent
	end if
%>
<form method="post" action=""  autocomplete="off">
<input type="hidden" name="action" value="updateContact">
<input type="hidden" name="contactid" value="<%=rs("ID")%>">
<label>First Name</label>
<input name="firstname" value="<%=rs("FirstName")%>">
<label>Last Name</label>
<input name="lastname" value="<%=rs("LastName")%>">
<label>Email</label>
<input name="email" value="<%=rs("Email")%>">
<label>username</label>
<input name="username"  value="<%=rs("UserName")%>">
<label>User Level</label>
<select name="userlevel">
	<option value="">No Log In</option>
    <option value="Admin"<%if rs("UserLevel")="Admin" then response.write " selected" end if%>>Admin</option>
    <option value="Support"<%if rs("UserLevel")="Support" then response.write " selected" end if%>>Support</option>
    <option value="Sales"<%if rs("UserLevel")="Sales" then response.write " selected" end if%>>Sales</option>
</select>
<p class="notice">
NOTE:<br>The current password can not be displayed.<br>
If the user forgot their password, you may set a new password by entering the password field below.
</p>
<p class="notice">Leave blank to keep current password.</p>
<label>New Password</label>
<input name="newpassword">
<button type="submit">Submit</button>
</form>
<%
else
	response.write "ERROR: Bad User ID"
end if
%>
</body>
</html>
<%
rs.Close()
Set rs = Nothing
%>


STYLE.CSS

.nav{
	padding:10px;
	font-size:115%;
	border-bottom:solid;
}
#login{
	width:500px;
	margin-left:auto;
	margin-right:auto;
}
#login form{
	font-size:110%;
}
#login input{
	
	color:grey;

}
.notice {
	font-size:85%;
	color:red;
}
.error {
	
	background-color:pink;
}
.success {
	background-color:LightGreen;
}
label{
	width:100px;
	 display:block
}
input,select{
	display:block;
	margin-bottom:10px;
}
table.usertable {
	width:600px;
}

table.usertable tr:nth-child(even) {
    background-color: #eee;
}
table.usertable tr:nth-child(odd) {
   background-color:#fff;
}
table.usertable th	{
    background-color: black;
    color: white;
}

INCLUDES/CONFIG.ASP


<%
option explicit 
' config and function variables
Dim conEE,secret_key,login_session_minutes,main_login_page
conEE = "Provider=SQLOLEDB; datasource=localhost; Database=db_name; Uid=username; Pwd=password"

' ---- config set up ---- 
secret_key 					= "aBc123Xyz456LMNop" ' use a long random string '
login_session_minutes	= 60 ' total time per session in minutes
main_login_page 		= "/ee/login/"  'where to redirect for the main log in page

%>

INCLUDES/FUNCTIONS.ASP


<%
DIM strAuth,logged_in_status,logged_in_user,logged_in_level,foundToken,arrAuth
DIM passHash,zPass,zUserID,zUserName,cmdFunction,rsFunction,fnPassword,token_expires,token,logged_in_expires

' to be placed at the top on every page you want secured'
logged_in_status = "0"
strAuth = lookupToken ' find token using function'
if strAuth <> "0" then ' has data
	arrAuth=split(strAuth,"|") ' create an array from our pipe delimited return'
	logged_in_status 	= arrAuth(0)
	logged_in_user 		= arrAuth(1)
	logged_in_level 		= arrAuth(2) ' Level_1, level_2, Level_3, Admin, Author, User'
	logged_in_expires   = arrAuth(3)
end if


' ---- Set a token to cookie and db ----'
function setToken(zUserID,zUserName)
	' logout any remaining tokens
	 logOutToken(zUserID)

	'set token in cookie and log in table for 1 hour
	token_expires = dateAdd("n",login_session_minutes,now)
	token = sha256(now&zUserName&secret_key)
	Response.Cookies("domaintoken")=token
	Response.Cookies("domaintoken").Expires=cdate(token_expires)

	Set cmdFunction = Server.CreateObject ("ADODB.Command")
	cmdFunction.ActiveConnection = conEE
	cmdFunction.CommandText = "INSERT INTO  ee_tLoginTrans (UserID,UserName,Token,TokenExpires,LoggedIP,LoggedTimeStamp) VALUES(?,?,?,?,?,?)"
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("username", 5, 1, -1, zUserID) 'int user id
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("username", 202, 1, 50, zUserName) 'string username
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("username", 202, 1, 350,token ) 'string token
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("username", 135, 1, -1, token_expires) 'string
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("username", 202, 1, 50,  Request.ServerVariables("REMOTE_ADDR")) 'string
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("username", 135, 1, -1, now) 'string
	cmdFunction.Prepared = true
	cmdFunction.Execute
	cmdFunction.ActiveConnection.Close

end function

' ------- LOG OUT - Remove token string from login trans ---------
function logOutToken(zUserID)
		Set cmdFunction = Server.CreateObject ("ADODB.Command")
		cmdFunction.ActiveConnection = conEE
		cmdFunction.CommandText = "UPDATE dbo.ee_tLoginTrans  SET Token='' WHERE UserID = ? AND Token<>''" 
		cmdFunction.Prepared = true
		cmdFunction.Parameters.Append cmdFunction.CreateParameter("userid", 5, 1, -1, zUserID) ' adDouble
		cmdFunction.Execute
		cmdFunction.ActiveConnection.Close

end function

' ---- look up cookie----'
function lookupToken()
dim rsFoundToken
	lookupToken="0" ' return bad '
	foundToken = Request.Cookies("domaintoken")
	
	if foundToken<>"" then ' if token cookie found then look up db'
		Set cmdFunction = Server.CreateObject ("ADODB.Command")
		cmdFunction.ActiveConnection = conEE
		cmdFunction.CommandText = "SELECT UserID, UserName, Token, TokenExpires, LoggedIP, LoggedTimeStamp, UserLevel FROM            ee_vLoggedInUsers WHERE Token = ?" 
		cmdFunction.Parameters.Append cmdFunction.CreateParameter("domaintoken", 202, 1, 350, request.Cookies("domaintoken")) '
		cmdFunction.Prepared = true
		Set rsFoundToken= cmdFunction.Execute
		
		If not rsFoundToken.bof or not rsFoundToken.eof then
			' pipe delimited return  username|admin'
			lookupToken="1|"&rsFoundToken("UserName")&"|"&rsFoundToken("UserLevel")&"|"&rsFoundToken("TokenExpires")
		end if
		
		rsFoundToken.Close()
		Set rsFoundToken = Nothing
	end if
end function


' ----Check Authentication ----'
' if you do not use levels, you can simply run this function at the top of every page.
function authenticate()
	if cstr(logged_in_status)="0" then
		response.redirect main_login_page ' not logged in, send away'
	end if
end function

' ---- Redirect Based On User Level ----'
function redirectLoginLevel(logged_in_level)

	select case logged_in_level
		case "Admin"
			response.Redirect("admin.asp")
		case "Support"
			response.Redirect("support.asp")
		case "Sales"
			response.Redirect("sales.asp")
	end select
	
end function


' ---- Update User Password----'
function setPassword(zPass,zUserID)

	' get the username
	zUserName=""
	
	Set cmdFunction = Server.CreateObject ("ADODB.Command")
	cmdFunction.ActiveConnection = conEE
	cmdFunction.CommandText = "SELECT * FROM dbo.ee_tUsers WHERE ID = ?" 
	cmdFunction.Parameters.Append cmdFunction.CreateParameter("userid", 4, 1, -1, zUserID) '
	cmdFunction.Prepared = true
	Set rsFunction = cmdFunction.Execute
	
	if not rsFunction.eof then
		zUserName=rsFunction("UserName")
	end if

	
	rsFunction.Close()
	Set rsFunction = Nothing

	' create the password hash
	if zUserName<> "" then
		passHash = passwordHash(zUserName,zPass)
		
		Set cmdFunction = Server.CreateObject ("ADODB.Command")
		cmdFunction.ActiveConnection = conEE
		cmdFunction.CommandText = "UPDATE dbo.ee_tUsers SET Password = ? WHERE ID = ?" 
		cmdFunction.Prepared = true
		cmdFunction.Parameters.Append cmdFunction.CreateParameter("param1", 202, 1, 350, passHash) ' adVarWChar
		cmdFunction.Parameters.Append cmdFunction.CreateParameter("param2", 5, 1, -1, zUserID) ' adDouble
		cmdFunction.Execute
		cmdFunction.ActiveConnection.Close
		
		setPassword = 1
		
		else
		
		setPassword=0
		
	end if
	
end function


function validatePassword(fnPassword)
	'http://www.mikesdotnetting.com/article/24/regular-expressions-and-vbscript
	' http://regexlib.com/Search.aspx?k=password&c=4&m=5&ps=20
	' This regex can be used to restrict passwords to a length of 8 to 20 aplhanumeric characters 
	'and select special characters. The password also can not start with a digit, underscore or special character
	' and must contain at least one digit.
	'Matches	password1 | pa$$word2 | pa!@#$%3
	'Non-Matches	password | 1stpassword | $password#
	'Author Michael Ash
				
	Dim re, targetString
	Set re = New RegExp
	With re
  		.Pattern = "^(?=[^\d_].*?\d)\w(\w|[!@#$%]){7,20}"
  		.Global = False
  		.IgnoreCase = False
	End With
	targetString = fnPassword

	validatePassword = re.Test(targetString)
	
							
end function



' ---- Hash a password ----'
function passwordHash(zUserName,zPass)
	passwordHash=sha256(zUserName&zPass&secret_key)
end function


' ---- SHA 256 FUNCTION ----
' See the VB6 project that accompanies this sample for full code comments on how
' it works.
'
' ASP VBScript code for generating a SHA256 'digest' or 'signature' of a string. The
' MD5 algorithm is one of the industry standard methods for generating digital
' signatures. It is generically known as a digest, digital signature, one-way
' encryption, hash or checksum algorithm. A common use for SHA256 is for password
' encryption as it is one-way in nature, that does not mean that your passwords
' are not free from a dictionary attack.
'
' If you are using the routine for passwords, you can make it a little more secure
' by concatenating some known random characters to the password before you generate
' the signature and on subsequent tests, so even if a hacker knows you are using
' SHA-256 for your passwords, the random characters will make it harder to dictionary
' attack.
'
' NOTE: Due to the way in which the string is processed the routine assumes a
' single byte character set. VB passes unicode (2-byte) character strings, the
' ConvertToWordArray function uses on the first byte for each character. This
' has been done this way for ease of use, to make the routine truely portable
' you could accept a byte array instead, it would then be up to the calling
' routine to make sure that the byte array is generated from their string in
' a manner consistent with the string type.
'
' This is 'free' software with the following restrictions:
'
' You may not redistribute this code as a 'sample' or 'demo'. However, you are free
' to use the source code in your own code, but you may not claim that you created
' the sample code. It is expressly forbidden to sell or profit from this source code
' other than by the knowledge gained or the enhanced value added by your own code.
'
' Use of this software is also done so at your own risk. The code is supplied as
' is without warranty or guarantee of any kind.
'
' Should you wish to commission some derivative work based on this code provided
' here, or any consultancy work, please do not hesitate to contact us.
'
' Web Site:  http://www.frez.co.uk
' E-mail:    sales@frez.co.uk

Private m_lOnBits(30)
Private m_l2Power(30)
Private K(63)

Private Const BITS_TO_A_BYTE = 8
Private Const BYTES_TO_A_WORD = 4
Private Const BITS_TO_A_WORD = 32

m_lOnBits(0) = CLng(1)
m_lOnBits(1) = CLng(3)
m_lOnBits(2) = CLng(7)
m_lOnBits(3) = CLng(15)
m_lOnBits(4) = CLng(31)
m_lOnBits(5) = CLng(63)
m_lOnBits(6) = CLng(127)
m_lOnBits(7) = CLng(255)
m_lOnBits(8) = CLng(511)
m_lOnBits(9) = CLng(1023)
m_lOnBits(10) = CLng(2047)
m_lOnBits(11) = CLng(4095)
m_lOnBits(12) = CLng(8191)
m_lOnBits(13) = CLng(16383)
m_lOnBits(14) = CLng(32767)
m_lOnBits(15) = CLng(65535)
m_lOnBits(16) = CLng(131071)
m_lOnBits(17) = CLng(262143)
m_lOnBits(18) = CLng(524287)
m_lOnBits(19) = CLng(1048575)
m_lOnBits(20) = CLng(2097151)
m_lOnBits(21) = CLng(4194303)
m_lOnBits(22) = CLng(8388607)
m_lOnBits(23) = CLng(16777215)
m_lOnBits(24) = CLng(33554431)
m_lOnBits(25) = CLng(67108863)
m_lOnBits(26) = CLng(134217727)
m_lOnBits(27) = CLng(268435455)
m_lOnBits(28) = CLng(536870911)
m_lOnBits(29) = CLng(1073741823)
m_lOnBits(30) = CLng(2147483647)

m_l2Power(0) = CLng(1)
m_l2Power(1) = CLng(2)
m_l2Power(2) = CLng(4)
m_l2Power(3) = CLng(8)
m_l2Power(4) = CLng(16)
m_l2Power(5) = CLng(32)
m_l2Power(6) = CLng(64)
m_l2Power(7) = CLng(128)
m_l2Power(8) = CLng(256)
m_l2Power(9) = CLng(512)
m_l2Power(10) = CLng(1024)
m_l2Power(11) = CLng(2048)
m_l2Power(12) = CLng(4096)
m_l2Power(13) = CLng(8192)
m_l2Power(14) = CLng(16384)
m_l2Power(15) = CLng(32768)
m_l2Power(16) = CLng(65536)
m_l2Power(17) = CLng(131072)
m_l2Power(18) = CLng(262144)
m_l2Power(19) = CLng(524288)
m_l2Power(20) = CLng(1048576)
m_l2Power(21) = CLng(2097152)
m_l2Power(22) = CLng(4194304)
m_l2Power(23) = CLng(8388608)
m_l2Power(24) = CLng(16777216)
m_l2Power(25) = CLng(33554432)
m_l2Power(26) = CLng(67108864)
m_l2Power(27) = CLng(134217728)
m_l2Power(28) = CLng(268435456)
m_l2Power(29) = CLng(536870912)
m_l2Power(30) = CLng(1073741824)

K(0) = &H428A2F98
K(1) = &H71374491
K(2) = &HB5C0FBCF
K(3) = &HE9B5DBA5
K(4) = &H3956C25B
K(5) = &H59F111F1
K(6) = &H923F82A4
K(7) = &HAB1C5ED5
K(8) = &HD807AA98
K(9) = &H12835B01
K(10) = &H243185BE
K(11) = &H550C7DC3
K(12) = &H72BE5D74
K(13) = &H80DEB1FE
K(14) = &H9BDC06A7
K(15) = &HC19BF174
K(16) = &HE49B69C1
K(17) = &HEFBE4786
K(18) = &HFC19DC6
K(19) = &H240CA1CC
K(20) = &H2DE92C6F
K(21) = &H4A7484AA
K(22) = &H5CB0A9DC
K(23) = &H76F988DA
K(24) = &H983E5152
K(25) = &HA831C66D
K(26) = &HB00327C8
K(27) = &HBF597FC7
K(28) = &HC6E00BF3
K(29) = &HD5A79147
K(30) = &H6CA6351
K(31) = &H14292967
K(32) = &H27B70A85
K(33) = &H2E1B2138
K(34) = &H4D2C6DFC
K(35) = &H53380D13
K(36) = &H650A7354
K(37) = &H766A0ABB
K(38) = &H81C2C92E
K(39) = &H92722C85
K(40) = &HA2BFE8A1
K(41) = &HA81A664B
K(42) = &HC24B8B70
K(43) = &HC76C51A3
K(44) = &HD192E819
K(45) = &HD6990624
K(46) = &HF40E3585
K(47) = &H106AA070
K(48) = &H19A4C116
K(49) = &H1E376C08
K(50) = &H2748774C
K(51) = &H34B0BCB5
K(52) = &H391C0CB3
K(53) = &H4ED8AA4A
K(54) = &H5B9CCA4F
K(55) = &H682E6FF3
K(56) = &H748F82EE
K(57) = &H78A5636F
K(58) = &H84C87814
K(59) = &H8CC70208
K(60) = &H90BEFFFA
K(61) = &HA4506CEB
K(62) = &HBEF9A3F7
K(63) = &HC67178F2

Private Function LShift(lValue, iShiftBits)
    If iShiftBits = 0 Then
        LShift = lValue
        Exit Function
    ElseIf iShiftBits = 31 Then
        If lValue And 1 Then
            LShift = &H80000000
        Else
            LShift = 0
        End If
        Exit Function
    ElseIf iShiftBits < 0 Or iShiftBits > 31 Then
        Err.Raise 6
    End If

    If (lValue And m_l2Power(31 - iShiftBits)) Then
        LShift = ((lValue And m_lOnBits(31 - (iShiftBits + 1))) * m_l2Power(iShiftBits)) Or &H80000000
    Else
        LShift = ((lValue And m_lOnBits(31 - iShiftBits)) * m_l2Power(iShiftBits))
    End If
End Function

Private Function RShift(lValue, iShiftBits)
    If iShiftBits = 0 Then
        RShift = lValue
        Exit Function
    ElseIf iShiftBits = 31 Then
        If lValue And &H80000000 Then
            RShift = 1
        Else
            RShift = 0
        End If
        Exit Function
    ElseIf iShiftBits < 0 Or iShiftBits > 31 Then
        Err.Raise 6
    End If

    RShift = (lValue And &H7FFFFFFE) \ m_l2Power(iShiftBits)

    If (lValue And &H80000000) Then
        RShift = (RShift Or (&H40000000 \ m_l2Power(iShiftBits - 1)))
    End If
End Function

Private Function AddUnsigned(lX, lY)
    Dim lX4
    Dim lY4
    Dim lX8
    Dim lY8
    Dim lResult

    lX8 = lX And &H80000000
    lY8 = lY And &H80000000
    lX4 = lX And &H40000000
    lY4 = lY And &H40000000

    lResult = (lX And &H3FFFFFFF) + (lY And &H3FFFFFFF)

    If lX4 And lY4 Then
        lResult = lResult Xor &H80000000 Xor lX8 Xor lY8
    ElseIf lX4 Or lY4 Then
        If lResult And &H40000000 Then
            lResult = lResult Xor &HC0000000 Xor lX8 Xor lY8
        Else
            lResult = lResult Xor &H40000000 Xor lX8 Xor lY8
        End If
    Else
        lResult = lResult Xor lX8 Xor lY8
    End If

    AddUnsigned = lResult
End Function

Private Function Ch(x, y, z)
    Ch = ((x And y) Xor ((Not x) And z))
End Function

Private Function Maj(x, y, z)
    Maj = ((x And y) Xor (x And z) Xor (y And z))
End Function

Private Function S(x, n)
    S = (RShift(x, (n And m_lOnBits(4))) Or LShift(x, (32 - (n And m_lOnBits(4)))))
End Function

Private Function R(x, n)
    R = RShift(x, CInt(n And m_lOnBits(4)))
End Function

Private Function Sigma0(x)
    Sigma0 = (S(x, 2) Xor S(x, 13) Xor S(x, 22))
End Function

Private Function Sigma1(x)
    Sigma1 = (S(x, 6) Xor S(x, 11) Xor S(x, 25))
End Function

Private Function Gamma0(x)
    Gamma0 = (S(x, 7) Xor S(x, 18) Xor R(x, 3))
End Function

Private Function Gamma1(x)
    Gamma1 = (S(x, 17) Xor S(x, 19) Xor R(x, 10))
End Function

Private Function ConvertToWordArray(sMessage)
    Dim lMessageLength
    Dim lNumberOfWords
    Dim lWordArray()
    Dim lBytePosition
    Dim lByteCount
    Dim lWordCount
    Dim lByte

    Const MODULUS_BITS = 512
    Const CONGRUENT_BITS = 448

    lMessageLength = Len(sMessage)

    lNumberOfWords = (((lMessageLength + ((MODULUS_BITS - CONGRUENT_BITS) \ BITS_TO_A_BYTE)) \ (MODULUS_BITS \ BITS_TO_A_BYTE)) + 1) * (MODULUS_BITS \ BITS_TO_A_WORD)
    ReDim lWordArray(lNumberOfWords - 1)

    lBytePosition = 0
    lByteCount = 0
    Do Until lByteCount >= lMessageLength
        lWordCount = lByteCount \ BYTES_TO_A_WORD

        lBytePosition = (3 - (lByteCount Mod BYTES_TO_A_WORD)) * BITS_TO_A_BYTE

        lByte = AscB(Mid(sMessage, lByteCount + 1, 1))

        lWordArray(lWordCount) = lWordArray(lWordCount) Or LShift(lByte, lBytePosition)
        lByteCount = lByteCount + 1
    Loop

    lWordCount = lByteCount \ BYTES_TO_A_WORD
    lBytePosition = (3 - (lByteCount Mod BYTES_TO_A_WORD)) * BITS_TO_A_BYTE

    lWordArray(lWordCount) = lWordArray(lWordCount) Or LShift(&H80, lBytePosition)

    lWordArray(lNumberOfWords - 1) = LShift(lMessageLength, 3)
    lWordArray(lNumberOfWords - 2) = RShift(lMessageLength, 29)

    ConvertToWordArray = lWordArray
End Function

Public Function SHA256(sMessage)
    Dim HASH(7)
    Dim M
    Dim W(63)
    Dim a
    Dim b
    Dim c
    Dim d
    Dim e
    Dim f
    Dim g
    Dim h
    Dim i
    Dim j
    Dim T1
    Dim T2

    HASH(0) = &H6A09E667
    HASH(1) = &HBB67AE85
    HASH(2) = &H3C6EF372
    HASH(3) = &HA54FF53A
    HASH(4) = &H510E527F
    HASH(5) = &H9B05688C
    HASH(6) = &H1F83D9AB
    HASH(7) = &H5BE0CD19

    M = ConvertToWordArray(sMessage)

    For i = 0 To UBound(M) Step 16
        a = HASH(0)
        b = HASH(1)
        c = HASH(2)
        d = HASH(3)
        e = HASH(4)
        f = HASH(5)
        g = HASH(6)
        h = HASH(7)

        For j = 0 To 63
            If j < 16 Then
                W(j) = M(j + i)
            Else
                W(j) = AddUnsigned(AddUnsigned(AddUnsigned(Gamma1(W(j - 2)), W(j - 7)), Gamma0(W(j - 15))), W(j - 16))
            End If

            T1 = AddUnsigned(AddUnsigned(AddUnsigned(AddUnsigned(h, Sigma1(e)), Ch(e, f, g)), K(j)), W(j))
            T2 = AddUnsigned(Sigma0(a), Maj(a, b, c))

            h = g
            g = f
            f = e
            e = AddUnsigned(d, T1)
            d = c
            c = b
            b = a
            a = AddUnsigned(T1, T2)
        Next

        HASH(0) = AddUnsigned(a, HASH(0))
        HASH(1) = AddUnsigned(b, HASH(1))
        HASH(2) = AddUnsigned(c, HASH(2))
        HASH(3) = AddUnsigned(d, HASH(3))
        HASH(4) = AddUnsigned(e, HASH(4))
        HASH(5) = AddUnsigned(f, HASH(5))
        HASH(6) = AddUnsigned(g, HASH(6))
        HASH(7) = AddUnsigned(h, HASH(7))
    Next

    SHA256 = LCase(Right("00000000" & Hex(HASH(0)), 8) & Right("00000000" & Hex(HASH(1)), 8) & Right("00000000" & Hex(HASH(2)), 8) & Right("00000000" & Hex(HASH(3)), 8) & Right("00000000" & Hex(HASH(4)), 8) & Right("00000000" & Hex(HASH(5)), 8) & Right("00000000" & Hex(HASH(6)), 8) & Right("00000000" & Hex(HASH(7)), 8))
End Function
%>

INCLUDES/NAV.ASP


<div><%
 if isArray(arrAuth) then ' 'if logged in
 	response.write "Welcome "&logged_in_user&" your session expires "&logged_in_expires
 end if 
 %>
</div>
<div class="nav">
	<a href="index.asp">Log In</a> |
    <a href="userAdd.asp">New User</a> |
	<a href="admin.asp">Admin </a> |
	<a href="loggedinusers.asp">Logged In Users |</a>
    <a href="sales.asp">Sales |</a>
    <a href="support.asp">Support |</a>
</div>


To add yourself as the first user, you will need to do this without having to log in. At the top of the UserAdd.asp, comment out the line 8, response.Redirect("index.asp"). We need this removed to allow you on the page. For the user level, set yourself as "Admin". The password must start with a character and contain at least one digit. For regular Expression help, you can do an advanced search on Experts Exchange http://www.experts-exchange.com/advancedSearch.jsp, click Question and select the topic Regular Expression. You can also find help at http://www.mikesdotnetting.com/article/24/regular-expressions-and-vbscript or http://regexlib.com/Search.aspx?k=password&c=4&m=5&ps=20.


Once you have created yourself as a user, add back the code on line 8,response.Redirect("index.asp"). Now you can go to your index page and log in. For this demo site, the Admin page lists all the users you have created. The LoggedInUsers.asp page shows who is logged in by using the view you created. Try creating a second user, then log in as that user on another browser. With your admin credentials, if you refresh the LoggedInUsers.asp page you can see two users. If you click the Submit button under the logout column for your second user, that user will be logged out and on the next page refresh will be sent back to main log in page. This is because we removed the token from the login transaction table. If you look at your database, you will see the row still exists, but the token field is missing.


Because there are several user levels, I created a function that will forward users to the different page once logged in based on their user credentials.


Now for the details of how this works. Starting with the first flow chart, we have a form with a username and password. Once submitted, the username is used to look up the users table with the following SQL where the ? = the form data for the username.

SELECT ID, UserName,UserLevel, Password FROM dbo.ee_tUsers WHERE Username = ? 


Assuming we find the username, the next step is to match the password entered with the password stored. I have hashed the password so there is no direct way to lookup the password directly from the form data. If you look at your users table, you will see the password looks something like this: cb0ef439f1865799621cfe8beff06261642ae1902145e9e35d8adfa7c0fb99da. It is an sha256 hash using the username password and secret key as the salt. This way two people with the same password will have a different hash because the usernames are unique. There are more secure hashing algorithm's and no matter which you choose; somebody will have an opinion. Since this is classic ASP, there is a readily available source to create this hash and that is why I have chosen it. There is a good article on this subject at crackstation https://crackstation.net/hashing-security.htm. As an alternative to hashing with VBscript, you can also do this on SQL Server with HASHBYTES https://msdn.microsoft.com/en-us/library/ms174415.aspx. If you go this route, you will want to create a stored procedure for these functions. This article will be talking about maintining the hashing functions in VBscript.


DECLARE @HashThis nvarchar(4000);
SET @HashThis = CONVERT(nvarchar(4000),'dslfdkjLK85kldhnv$n000#knf');
SELECT HASHBYTES('SHA1', @HashThis);


Because the stored password is hashed in our database, the way to check if the password is correct is to run the submitted password through our hashing function then match the result with what is stored in the database.


The function that does this is below. The sha256 function is the large function at the bottom of the functions.asp page. It is simply creating the hash as sha256("sometext"). For the text, we are concatenating the username, password and secret key. This is what is referred to as salting.

' ---- Hash a password ----'
function passwordHash(zUserName,zPass)
    passwordHash=sha256(zUserName&zPass&secret_key)
end function


On the index.asp code if there is a submitted form with the field username, we our look up of the user table. Then run the submitted password through the passwordHash function and match that result with the password found in the database.


If the password is a match, we create a token which is another salted hash. This hash is set as a cookie and stored in the LoginTrans table using the field named Token.


On every subsequent page load, we look up the cookie to get the token, then search for the token in the LoginTrans table. If found, test that the expiration date is good and proceed.


At the top of ever page we get the status, logged in user, their level and expires from the log in trans. The lookupToken function searches the database for the token found in the cookie then returns a pipe delimited response.

logged_in_status = "0"
strAuth = lookupToken ' find token using function'
if strAuth <> "0" then ' has data
    arrAuth=split(strAuth,"|") ' create an array from our pipe delimited return'
    logged_in_status     = arrAuth(0)
    logged_in_user         = arrAuth(1)
    logged_in_level     = arrAuth(2) ' Level_1, level_2, Level_3, Admin, Author, User'
    logged_in_expires   = arrAuth(3)
end if

' ---- look up cookie----'
function lookupToken()
dim rsFoundToken
    lookupToken="0" ' return bad '
    foundToken = Request.Cookies("domaintoken")
    
    if foundToken<>"" then ' if token cookie found then look up db'
        Set cmdFunction = Server.CreateObject ("ADODB.Command")
        cmdFunction.ActiveConnection = conEE
        cmdFunction.CommandText = "SELECT UserID, UserName, Token, TokenExpires, LoggedIP, LoggedTimeStamp, UserLevel FROM            ee_vLoggedInUsers WHERE Token = ?" 
        cmdFunction.Parameters.Append cmdFunction.CreateParameter("domaintoken", 202, 1, 350, request.Cookies("domaintoken")) '
        cmdFunction.Prepared = true
        Set rsFoundToken= cmdFunction.Execute
        
        If not rsFoundToken.bof or not rsFoundToken.eof then
            ' pipe delimited return  username|admin'
            lookupToken="1|"&rsFoundToken("UserName")&"|"&rsFoundToken("UserLevel")&"|"&rsFoundToken("TokenExpires")
        end if
        
        rsFoundToken.Close()
        Set rsFoundToken = Nothing
    end if
end function


It is important to have unique usernames and there is a function that helps when a username is selected. In both the Edit and Add user pages, there is a look up that makes sure the username is unique.  

    ' check if username is unique
        if cmdUsername<>"" then ' allow blank usernames
            Set cmd = Server.CreateObject ("ADODB.Command")
            cmd.ActiveConnection = conEE
            cmd.CommandText = "SELECT ID FROM dbo.ee_tUsers WHERE UserName = ?" 
            cmd.Parameters.Append cmd.CreateParameter("username", 202, 1, 50, cmdUsername)
            cmd.Prepared = true
            Set rs = cmd.Execute
            
            if not rs.eof then ' the username is in use, don't update
                validation_username=0
            end if
        
            rs.Close()
            Set rs = Nothing
        end if

For the page authentication, at the top of every page you will see code below. What is happening is the functions.asp page is loading and retrieving the cookie with the token and doing the look up of the user and placing the data into several fields. One field is status and another is user level. In the code directly below we are looking to see if the status is "0" or if the words, "Admin" or "Support" are not found. If that is the case, we redirect them to the index page. If you have multiple user levels that are allowed on the page, just add more seperated by a space.  


if logged_in_status = "0" OR  instr("Support Admin",logged_in_level)<1 then
	response.Redirect("index.asp")
end if

If you allow multiple user levels on one page and want to restrict a specific portion of one page to only one user level, you can use the sample code in the support.asp page. Here we are using the variable logged_in_level and testing if it is set to "Admin". If it is, then show the html inside the if/then.


<h1>Support</h1>
<div>This section can be seen by Support and Admin</div>
<%
if logged_in_level = "Admin" then
%>
<div>This section can be seen ONLY by Admin</div>
<%
end if
%>


If your page will only have one user level, you can use the code below instead. 

if logged_in_status = "0" OR logged_in_level<>"Admin" then
	response.Redirect("index.asp")
end if


Because we are adding a row of data to the login trans table, you could develop a report that shows all past logins, how many times any one user logged in, how many times all users logged in between two dates or there are APIs you can use to lookup an IP and get its location.


Another option you may want to impliment for your users is showing the amount of time they have left until their session expires. A simple way is to simply display the logged_in_expires field.

response.write logged_in_expires

This will show the full date and time staticly. If you would like to show some type of countdown timer, I would suggest creating an animated countdown timer in javascript or use a ready made project. For the start time, you can use the field logged_in_expires.


You choose an animated countdown timer such as http://hilios.github.io/jQuery.countdown/. They show a code sample:

<div id="getting-started"></div>
 <script type="text/javascript">
   $("#getting-started")
   .countdown("2016/01/01", function(event) {
     $(this).text(
       event.strftime('%D days %H:%M:%S')
     );
   });
 </script>

The modification you would need to make could be:


<div id="getting-started"></div>
 <script type="text/javascript">
<%
dim countdown_time
countdown_time=year(logged_in_expires)&"/"& right("0"&month(logged_in_expires),2)&"/"&right("0"&day(logged_in_expires),2)
%>
   $("#getting-started")
   .countdown("<%=countdown_time%>", function(event) {
     $(this).text(
       event.strftime('%D days %H:%M:%S')
     );
   });
 </script>


On the config.asp page, we are setting the default log in time for 60 minutes. Feel free to change that to whatever number you want. If you prefer to allow people to be logged in for a specific number of days, weeks or months instead of  minutes, you can either do the conversion to minutes yourself, or find the line of code in the functions.asp page that sets the token_expires variable. The letter "n" signifies minutes. If you want to use days, use a "d" or "m" for months. http://www.w3schools.com/vbscript/func_dateadd.asp   

token_expires = dateAdd("n",login_session_minutes,now)

You will also see a secret_key variable on the config.asp page. Make sure to change this to something else. It can be any combination of characters you want. Just make sure it is random and long.

5
14,125 Views
Scott FellDeveloper & Coffee Roaster
CERTIFIED EXPERT
I have over 30 years experience in Sales, Marketing, Technology and Developing solutions.

Comments (7)

Hi there. I know this article is quite old, but I've started using the script on a client's website and it works brilliantly. I've managed to get it working perfectly and I'm very happy with it. However, my client has just asked me to change the option from using a 'UserName' to an Email address instead.
I've tried this a couple of ways: either drop the email address in the Username column, or compare the username log-in field with the email column in the database. Neither of these seem to work. I'm not sure if this is due to the salting/hashing - and maybe the use of '@' or '.' in the email address/username?
It seems an ID is never found, so nothing is inserted into the 'login/trans' table.
If possible, please could you advise the best way of using an email address instead? Hope you can help, many thanks.
Scott FellDeveloper & Coffee Roaster
CERTIFIED EXPERT
Fellow
Most Valuable Expert 2013

Author

Commented:
If you just use an email address for the username it is not working?  In other words, if you input "sadrobotx" as the uername it works but if you use "sadrobotx@mydomain.com" it does not?
Hi there - thanks for your quick response. I feel like I've wasted your time, though! I have managed to sort it - it turns out I had previously altered how the salt is generated (I changed it to use the email address when creating the user), but I've swapped it back to using the username, and it works fine now. I do apologise! All sorted now though. Thanks again.
Scott FellDeveloper & Coffee Roaster
CERTIFIED EXPERT
Fellow
Most Valuable Expert 2013

Author

Commented:
I'm glad you have it working

Commented:
Hi there, Scott!

I received an automatic e-mail when this article was "activated". Now I realize I have never come back to give a feedback... I am sorry!

I implemented your great ideas many many years ago and the token is still being used on a daily basis! It never gave ANY trouble. NEVER!

All the best!
Fabio.

P.S.; I am glad sadrobotx could solve the problem!

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.