In 2000, if you were building a web application for a corporate environment running Windows servers, you used Active Server Pages. Microsoft's IIS + ASP was the enterprise stack: Windows NT 4 or Windows 2000 Server, IIS 5, SQL Server 7 or 2000, and ASP scripting in VBScript.
PHP ran on Unix and required Apache. Perl required knowing Unix. ASP ran on Windows, was managed via familiar Windows tools, integrated with Active Directory, and used VBScript - a language that IT departments already knew from Office macro automation. For businesses already invested in Microsoft infrastructure, ASP was the obvious path.
Building a login system with session state was the first real web application task. Here is the complete implementation from 2000.
The Session Object: ASP's Built-in State Management
ASP's Session object was a server-side dictionary per user, keyed by a cookie (ASPSESSIONID) set automatically by IIS. No manual cookie management required - IIS handled the session cookie, and your code wrote to and read from Session like a dictionary.
<%
' login.asp - Classic ASP login page with SQL Server authentication
' VBScript, IIS 5, SQL Server 2000, circa 2000
Option Explicit
Dim strError
strError = ""
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim strUsername, strPassword
strUsername = Trim(Request.Form("username"))
strPassword = Trim(Request.Form("password"))
' Basic input validation
If strUsername = "" Or strPassword = "" Then
strError = "Username and password are required."
Else
' Authenticate against database
Dim bAuthenticated
bAuthenticated = AuthenticateUser(strUsername, strPassword)
If bAuthenticated Then
' Store user identity in session
Session("IsLoggedIn") = True
Session("Username") = strUsername
Session("LoginTime") = Now()
' Redirect to protected area
Response.Redirect "dashboard.asp"
Response.End
Else
strError = "Invalid username or password."
End If
End If
End If
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Login</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body bgcolor="#FFFFFF">
<h2>Member Login</h2>
<% If strError <> "" Then %>
<p><font color="red"><b><%= Server.HTMLEncode(strError) %></b></font></p>
<% End If %>
<form method="POST" action="login.asp">
<table border="0" cellpadding="4">
<tr>
<td>Username:</td>
<td><input type="text" name="username" size="25"></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" size="25"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Log In"></td>
</tr>
</table>
</form>
</body>
</html>
Database Authentication with ADODB
<%
' Included as a function in login.asp or a separate include file
Function AuthenticateUser(strUsername, strPassword)
AuthenticateUser = False
Dim oConn, oRS
Dim strSQL
' Create ADODB Connection object - the COM-based database interface
Set oConn = Server.CreateObject("ADODB.Connection")
' DSN-less connection string to SQL Server 2000
oConn.Open "Provider=SQLOLEDB;" & _
"Data Source=SQLSERVER01;" & _
"Initial Catalog=MyAppDB;" & _
"User ID=webapp_user;" & _
"Password=webapp_pass;"
' WARNING: In 2000, many developers concatenated strings directly.
' This was vulnerable to SQL injection. The correct approach even then:
' use parameterized queries via ADODB Command object (shown below).
Set oRS = Server.CreateObject("ADODB.Recordset")
' Parameterized query - the correct 2000-era approach
Dim oCmd
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
oCmd.CommandText = "SELECT UserID, Username, PasswordHash, IsActive " & _
"FROM Users WHERE Username = ?"
oCmd.CommandType = 1 ' adCmdText
' Append parameter - prevents SQL injection
oCmd.Parameters.Append oCmd.CreateParameter("@username", 200, 1, 50, strUsername)
' 200 = adVarChar, 1 = adParamInput, 50 = max length
Set oRS = oCmd.Execute()
If Not oRS.EOF Then
Dim storedHash
storedHash = oRS("PasswordHash")
' Compare password hash - MD5 was standard in 2000
' (bcrypt didn't become common for web apps until ~2007-2010)
If MD5Hash(strPassword) = storedHash Then
If CBool(oRS("IsActive")) Then
AuthenticateUser = True
' Log the login event
Call LogLoginAttempt(CStr(oRS("UserID")), True)
End If
Else
Call LogLoginAttempt(CStr(oRS("UserID")), False)
End If
End If
oRS.Close
oConn.Close
Set oRS = Nothing
Set oCmd = Nothing
Set oConn = Nothing
End Function
%>
Protecting Pages with Session Checks
Every protected page included a session check at the top - ASP #include directives let you share this logic:
<%
' auth_check.asp - included at the top of every protected page
' Usage: <!-- #include file="auth_check.asp" -->
If Not CBool(Session("IsLoggedIn")) Then
Response.Redirect "login.asp?return=" & Server.URLEncode(Request.ServerVariables("URL"))
Response.End
End If
' Session timeout check - 30 minutes of inactivity
Dim dtLoginTime
dtLoginTime = Session("LoginTime")
If IsDate(dtLoginTime) Then
If DateDiff("n", dtLoginTime, Now()) > 30 Then
' Session expired - clear it and redirect
Session.Abandon
Response.Redirect "login.asp?msg=timeout"
Response.End
End If
End If
' Update last activity time
Session("LoginTime") = Now()
%>
A protected page:
<%
' dashboard.asp - member-only page
Option Explicit
%>
<!-- #include file="auth_check.asp" -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><title>Dashboard</title></head>
<body>
<h1>Welcome, <%= Server.HTMLEncode(Session("Username")) %>!</h1>
<p>You are logged in. <a href="logout.asp">Log out</a></p>
</body>
</html>
Logout:
<%
' logout.asp
Session.Abandon ' Clears all session variables and invalidates the session cookie
Response.Redirect "login.asp"
Response.End
%>
The SQL Server Table Structure
-- SQL Server 2000 schema
-- Run in Query Analyzer
CREATE TABLE Users (
UserID INT IDENTITY(1,1) PRIMARY KEY,
Username VARCHAR(50) NOT NULL UNIQUE,
PasswordHash VARCHAR(32) NOT NULL, -- MD5 hex string, 32 characters
Email VARCHAR(100) NOT NULL,
IsActive BIT NOT NULL DEFAULT 1,
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
LastLogin DATETIME NULL
)
GO
-- Index on Username for fast lookups
CREATE UNIQUE INDEX IX_Users_Username ON Users (Username)
GO
-- Login audit log
CREATE TABLE LoginLog (
LogID INT IDENTITY(1,1) PRIMARY KEY,
UserID INT NOT NULL,
LoginDate DATETIME NOT NULL DEFAULT GETDATE(),
IPAddress VARCHAR(15) NOT NULL,
Success BIT NOT NULL
)
GO
Inserting a user with a hashed password from ASP:
<%
' register.asp - create a new user account
' Hash password before storing - never store plaintext
' MD5 via CAPICOM or custom implementation
' Many teams used a third-party MD5.asp component from ASPFaq.com
' Common pattern in 2000: download a free "MD5.asp" include file
' <!-- #include file="md5.asp" -->
Dim strHashedPassword
strHashedPassword = MD5(Request.Form("password"))
Dim oConn, oCmd
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open strConnectionString
Set oCmd = Server.CreateObject("ADODB.Command")
Set oCmd.ActiveConnection = oConn
oCmd.CommandText = "INSERT INTO Users (Username, PasswordHash, Email) VALUES (?, ?, ?)"
oCmd.Parameters.Append oCmd.CreateParameter("@u", 200, 1, 50, strUsername)
oCmd.Parameters.Append oCmd.CreateParameter("@ph", 200, 1, 32, strHashedPassword)
oCmd.Parameters.Append oCmd.CreateParameter("@email", 200, 1, 100, strEmail)
oCmd.Execute
oConn.Close
Set oCmd = Nothing
Set oConn = Nothing
%>
What Made ASP Classic Distinct in 2000
COM integration. ASP could instantiate any COM object registered on the server: Server.CreateObject("MSWC.AdRotator"), Server.CreateObject("MSWC.BrowserType"), custom VB6 DLLs. This let developers write performance-critical logic in compiled Visual Basic and call it from ASP scripts.
The Application object. While Session was per-user, Application was shared across all users:
<%
' application.asp - called from Global.asa Application_OnStart
' Shared state: connection string, site configuration, cached data
Application.Lock
Application("ConnectionString") = "Provider=SQLOLEDB;Data Source=..."
Application("SiteVersion") = "2.1.4"
Application("StartTime") = Now()
Application.Unlock
%>
Global.asa. The application event file, analogous to a modern framework's bootstrap:
' Global.asa - placed in the web root
' Fires on application start/end and session start/end
<SCRIPT LANGUAGE="VBScript" RUNAT="Server">
Sub Application_OnStart
Application("TotalVisits") = 0
End Sub
Sub Session_OnStart
Application.Lock
Application("TotalVisits") = Application("TotalVisits") + 1
Application.Unlock
Session.Timeout = 30 ' 30 minutes session timeout
End Sub
Sub Session_OnEnd
' Clean up per-session resources
End Sub
</SCRIPT>
The Transition: ASP to ASP.NET
In 2000-2001, Microsoft was building ASP.NET, released in January 2002. The architectural shift was significant: from interpreted VBScript to compiled C# or VB.NET, from manual session/form management to the Web Forms model with server-side controls, from ADODB.Recordset to ADO.NET datasets.
Classic ASP developers who invested time in understanding the Session/Application/Request/Response object model found the transition to ASP.NET natural - the same abstractions existed, better implemented. Classic ASP continued running on IIS for years after ASP.NET's release; many production applications ran on it until Windows Server 2008 era, when hosting companies began deprecating it.
The patterns - session-based authentication, parameterized database queries, server-side rendering with template inclusion - are still the patterns of modern web development. The technology changed; the architecture did not.
Aunimeda builds production-grade backend systems - APIs, microservices, real-time applications, and system integrations.
Contact us for backend engineering services. See also: Custom Software Development, Web Development