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.
The 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.
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.
Comments (7)
Commented:
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.
Author
Commented:Commented:
Author
Commented:Commented:
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