This module is a form-based authentication module
based on '
mod_auth_mysql'
and
'
mod_auth_sim'.
It is used to place access restrictions on a
per-directory, per-user-request basis using session management.
The module uses a MySQL database to retrieve users' group
membership, maintain and validate users' sessions, and
optionally track user activity.
The mechanics of the module works in the following way. A
web client (user) requests for a restricted page/directory.
The module sends back a 'Page Has Moved' error, pointing
the client to the page containing a login form. Through
server-side scripting, a session is created in the MySQL
database and the client (i.e. cookies or query string).
The session itself consists of a unique, random, and
temporary ID that is associated with a user. The client
then makes the same request along with the session ID
(SID) and the user ID (UID). The module compares and
validates the two IDs against the IDs stored in the MySQL
database. If successful, the module sends back the requested
page; otherwise, the module once again sends back the
'Page Has Moved' error page. In addition (if specified),
the module will also validate the user's group membership
and act accordingly.
Additional Notes:
1) This module does not verify username and passwords. The
verification is left up to the web developer via
server-side scripting. For example, if .htpasswd files
are used, a Perl script can be written to verify a user's
input against a .htpasswd file; if the user is verified, the
same Perl script will create a session in the user's browser
(cookies or query string) and in a MySQL database.
2) Do not use Apache's basic authentication directives with
this module (exceptions are 'AuthType Basic' and 'AuthName
"Name"'). Doing so causes Apache to use basic authentication
whenever a user accesses a restricted page. (See
mod_auth
and similiar modules).
Case Study 1: A Simple Restricted Area:
A website will contain a restricted area that requires a
valid username and password. The restricted area itself
is a directory called '/restricted'; the login pages for that
restricted directory are '/login.html' and its handler '/login.php'.
The MySQL server resides on the same computer as the Apache server, and
the name of the database is 'users'. To implement this restricted
area, the following must be done:
- httpd.conf
LoadModule auth_form_module modules/mod_auth_form.so
<Directory "/restricted">
AuthType Basic
AuthName "Restricted"
AuthFormMySQLUsername my_username
AuthFormMySQLPassword my_password
AuthFormMySQLDB users
AuthFormSessionCookies On
AuthFormPageLogin /login.html
Require valid-user
</Directory>
- MySQL Database: users
- Table: sessions
- Field: VARCHAR (32): sid (PRIMARY KEY)
- Field: VARCHAR (20): uid
- Table: passwords
- Field: VARCHAR (20): uid (PRIMARY KEY)
- Field: VARCHAR (32): password (MD5 CRYPTED)
- /login.html
<html>
<head>
...
</head>
<body>
<form action="/login.php" method="post">
<input name="uid" type="text" maxlength="20">
<input name="password" type="password" maxlength="20">
<input type="submit" value="Login">
</form>
</body>
</html>
- /login.php
<?php
function genID($seed, $length)
{
$ID = "";
srand($seed);
for($i = 0; $i < $length; $i++)
{
$chtype = rand(1, 3);
switch($chtype)
{
case 1: // 0-9
$ID .= chr(rand(48, 57));
break;
case 2: // A-Z
$ID .= chr(rand(65, 90));
break;
case 3: // a-z
$ID .= chr(rand(97, 122));
break;
}
}
return $ID;
}
function create_session($mysql, $uid, $password)
{
//
// Build list of existing SIDs
//
$result = $mysql->query("SELECT sid FROM sessions");
$num_rows = $result->num_rows;
while($num_rows > 0)
{
$row = $result->fetch_assoc();
$sids[$row["sid"]] = TRUE;
$num_rows--;
}
$result->close();
//
// Generate SID (making sure it is unique)
//
$max_attempts = 500000;
$seed = crc32($password);
do
{
$sid = genID($seed + time(), 32);
$max_attempts--;
} while(isset($sids[$sid]) && $max_attempts > 0);
if($max_attempts <= 0) // NOT GOOD
return FALSE;
//
// Create the session: set the UID and SID in both the client's cookies and
// the MySQL session table.
//
$mysql->query("INSERT INTO sessions (sid, uid) VALUES ('$sid', '$uid')");
setcookie("uid", $uid, time() + 964224000);
setcookie("sid", $sid, time() + 964224000);
return TRUE;
}
$uid = $_POST["uid"];
$password = $_POST["password"];
$mysql = new mysqli("localhost", "my_username", "my_password", "users");
$result = $mysql->query("SELECT password FROM passwords WHERE uid='$uid'");
$row = $result->fetch_assoc();
$real_password = $row["password"];
$result->close();
if(md5($password) == $real_password)
{
create_session($mysql, $uid, $real_password);
header("Location: /restricted");
}
$mysql->close();
?>
Case Study 2: A Complex Member's Area:
A website will contain a member's area that requires a valid username and password.
The member's area itself is a directory called '/members'; furthermore, there is
another restricted directory called '/members/paying', which is for paying members
only (where as '/members' is for any member). For added security, all sessions will
expire either from 30 minutes of inactivity of the member or 8 hours from the time
of login. The login pages for the member's area are '/member_login.html' and its
handler '/member_login.php'. The MySQL server resides on a host called
'fake_server.com', and the name of the database is 'members'. Also, the MySQL server
will keep tracking records of each request made within the member's area. To
implement this member's area, the following must be done:
- httpd.conf
LoadModule auth_form_module modules/mod_auth_form.so
<Directory "/members">
AuthType Basic
AuthName "Member's Area"
AuthFormMySQLHost fake_server.com
AuthFormMySQLUsername my_username
AuthFormMySQLPassword my_password
AuthFormMySQLDB members
AuthFormMySQLTableGID creds
AuthFormMySQLTableTracking tracking
AuthFormMySQLFieldExpiration expiration_date
AuthFormSessionTimeout 30
AuthFormSessionCookies On
AuthFormPageLogin /member_login.html
AuthFormPageExpired /session_expired.html
AuthFormPageNotAllowed /paying_members_only.html
Require group non-paying paying
</Directory>
<Directory "/members/paying">
Require group paying
</Directory>
- MySQL Database: members
- Table: sessions
- Field: VARCHAR (32): sid (PRIMARY KEY)
- Field: VARCHAR (20): uid
- Field: DATETIME: timeout_date
- Field: DATETIME: expiration_date
- Table: creds
- Field: VARCHAR (20): uid (PRIMARY KEY)
- Field: VARCHAR (32): password (MD5 CRYPTED)
- Field: VARCHAR (15): gid ('paying' or 'non-paying')
- Table: tracking
- Field: VARCHAR (20): uid (PRIMARY KEY)
- Field: VARCHAR (15): client_ip_address
- Field: DATETIME: download_date
- Field: VARCHAR (255); download_path
- Field: INT (8) UNSIGNED: download_size
- /member_login.php
<?php
//
// Assume the function 'genID' from the previous case study is defined.
//
function create_session($mysql, $uid, $password)
{
//
// Build list of existing SIDs
//
$result = $mysql->query("SELECT sid FROM sessions");
$num_rows = $result->num_rows;
while($num_rows > 0)
{
$row = $result->fetch_assoc();
$sids[$row["sid"]] = TRUE;
$num_rows--;
}
$result->close();
//
// Generate SID (making sure it is unique)
//
$max_attempts = 500000;
$seed = crc32($password);
do
{
$sid = genID($seed + time(), 32);
$max_attempts--;
} while(isset($sids[$sid]) && $max_attempts > 0);
if($max_attempts <= 0) // NOT GOOD
return FALSE;
//
// Create the session: set the UID and SID in both the client's cookies and
// the MySQL session table.
//
$mysql->query("INSERT INTO sessions (sid, uid, timeout_date, expiration_date)
VALUES ('$sid', '$uid', DATE_ADD(NOW(), INTERVAL 30 MINUTE),
DATE_ADD(NOW(), INTERVAL 8 HOUR))");
setcookie("uid", $uid, time() + 964224000);
setcookie("sid", $sid, time() + 964224000);
return TRUE;
}
$uid = $_POST["uid"];
$password = $_POST["password"];
$mysql = new mysqli("fake_server.com", "my_username", "my_password", "members");
$result = $mysql->query("SELECT password FROM creds WHERE uid='$uid'");
$row = $result->fetch_assoc();
$real_password = $row["password"];
$result->close();
if(md5($password) == $real_password)
{
create_session($mysql, $uid, $real_password);
header("Location: /members");
}
$mysql->close();
?>
Logging Out:
Besides session expiration, logging out of a session is done simply by deleting the
session's record from the MySQL database. In addition, the client's cookies can be
expired if cookies are used. Here is a PHP script for destroying a session, assuming
the client passes the session cookies.
<?php
$uid = $_COOKIE["uid"];
$sid = $_COOKIE["sid"];
$mysql = new mysqli("mysql_host", "my_username", "my_password", "my_database");
$mysql->query("DELETE FROM sessions WHERE sid='$sid'");
$mysql->close();
setcookie("uid", $uid, time() - 964224000);
setcookie("sid", $sid, time() - 964224000);
header("Location: login_page");
?>
AuthFormMySQLHost
String : DEFAULT="localhost"
The fully-qualified name or IP address of the
MySQL server.
AuthFormMySQLUsername
String : REQUIRED
The MySQL user used to connect to the MySQL
server.
AuthFormMySQLPassword
String : REQUIRED
The MySQL user's password.
AuthFormMySQLDB
String : REQUIRED
The MySQL database to connect to.
AuthFormMySQLTableSID
String : DEFAULT="sessions"
The table that stores session records. At
minimum, the table should have a SID field and
a UID field where the SID field is the primary key.
AuthFormMySQLTableSIDCondition
String : OPTIONAL
Condition to add to the WHERE-clause when querying
the session table.
AuthFormMySQLTableGID
String : OPTIONAL
The table that stores the group memberships.
AuthFormMySQLFieldGID and
AuthFormPageNotAllowed must also be configured
along with the
Require directive in
'httpd.conf' (refer the the
Apache2 documentation for more information).
Also, the table must have a UID and GID field where
the UID field is the primary key.
AuthFormMySQLTableGIDCondition
String : OPTIONAL
Condition to add to the WHERE-clause when
querying the group membership table.
AuthFormMySQLTableTracking
String : OPTIONAL
The table that stores request tracking information.
The table must have a field for UID, client's IP
address, download date, download path, and download
size (no primary keys).
AuthFormMySQLTableTrackingCondition
String : OPTIONAL
Condition to add to the WHERE-clause when
querying the tracking table.
AuthFormMySQLFieldUID
String : DEFAULT="uid"
Field under the session, group, and tracking
tables that stores the user ID.
AuthFormMySQLFieldSID
String : DEFAULT="sid"
Field under the session table that stores
the session ID.
AuthFormMySQLFieldGID
String : DEFAULT="gid"
Field under the group table that stores
the user's space/comma delimited list
of group IDs.
AuthFormMySQLFieldTimeout
String : DEFAULT="timeout_date"
Field under the session table that stores
the time the session will expire if the user
is inactive.
AuthFormMySQLFieldExpiration
String : OPTIONAL
Field under the session table that stores
the time the session will expire regardless
of user activity. Not specifying this configuration
disables session expiration (although session inactivity
timeout can still be enabled). Also,
AuthFormPageExpired must be configured.
AuthFormMySQLFieldIPAddress
String : DEFAULT="client_ip_address"
Field under the tracking table that stores
the client's IP address.
AuthFormMySQLFieldDownloadDate
String : DEFAULT="download_date"
Field under the tracking table that stores
the time of request.
AuthFormMySQLFieldDownloadPath
String : DEFAULT="download_path"
Field under the tracking table that stores
the path of request.
AuthFormMySQLFieldDownloadSize
String : DEFAULT="download_size"
Field under the tracking table that stores
the size of request.
AuthFormSessionKeyUID
String : DEFAULT="uid"
Name of the key that stores the UID value
sent by the web client.
AuthFormSessionKeySID
String : DEFAULT="sid"
Name of the key that stores the SID value
sent by the web client.
AuthFormSessionTimeout
Number : DEFAULT=0
The session inactivity timeout in minutes. A
value of '0' indicates no timeout. Also,
AuthFormPageExpired must be configured.
AuthFormSessionCookies
Flag : DEFAULT="Off"
Whether to use cookies or the URL query string
to pass the session keys from the client to the
module. ('On' means use cookies; 'Off' means
use the URL query string).
AuthFormTrackingLifetime
Number : DEFAULT=30
Maximum number of days to hold a tracking record.
A value of '0' indicates infinite lifetime.
(The module goes by the date of download field under
the tracking table).
AuthFormPageLogin
String : REQUIRED
The URL to the page containing the login form.
AuthFormPageExpired
String : OPTIONAL
The URL to the 'session expired' page.
AuthFormPageNotAllowed
String : OPTIONAL
The URL to the 'invalid group member' page.
AuthFormAuthoritative
Flag : DEFAULT="On"
Is this module the final authority?