I want to build a registration system that sends an email to the user requiring validation of the email address before permitting access to restricted pages.
Restrict access to the site using a login system that requires authentication not only on the basis of username and password, but also access level. When the user registers, initially set the access level to a value that denies access, and send an email containing a unique token to the registered address. Update the user's access level when the user replies to the email using the token stored in the database.
This recipe is split into two parts: the registration process and the confirmation process. Sample files of the code at different stages of the registration process are attached in register.zip at the foot of this page.
Create a database table called users with the following structure (you can add extra columns, but this is the minumum):
user_id int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
username varchar(15) NOT NULL UNIQUE,
password varchar(40) NOT NULL,
email varchar(100) NOT NULL,
verified enum('n','y') NOT NULL DEFAULT 'n',
token varchar(40) NOT NULL
Points to note about the table are that
username is defined as having a UNIQUE index, and the
value of the
verified column is set to
n by default. The UNIQUE index ensures that the same
username cannot be inserted twice.
Create a registration form with two text input fields named
username and
email. There should also be two password fields named
password and
password2, as well as a submit button and a hidden
field named
token. The hidden field is simply to make it easier to
apply the Insert Record server behavior. For security purposes, the
token will not appear in the HTML code. It will only be stored in
the database and sent to the user's registered email address.
A sample form is in registration1.php in the zip file attached at the foot of this page.
Apply an Insert Record server behavior to the page containing
the registration form. There are no fields for
user_id or
verified, so the Insert Record dialog box will show
these as receiving no value. The primary key is incremented
automatically, and you want
verified to use the default value,
n.
Switch to Code view, and locate the following section of code:
if ((isset($_POST["MM_insert"])) && ($_POST["MM_insert"] ==
"form1")) {
$insertSQL = sprintf("INSERT INTO users (username, password,
email, token) VALUES (%s, %s, %s, %s)",
GetSQLValueString($_POST['username'], "text"),
GetSQLValueString($_POST['password'], "text"),
GetSQLValueString($_POST['email'], "text"),
GetSQLValueString($_POST['token'], "text"));
mysql_select_db($database_cookbook, $cookbook);
$Result1 = mysql_query($insertSQL, $cookbook) or
die(mysql_error());
}
This is the main section of the Insert Record server behavior code, which you need to edit to validate the user input, generate the token, and send the email. From this point on, the server behavior will no longer be editable through the Insert Record dialog box. All the work is done in Code view.
Amend the code shown in the preceding section like this:
if ((isset($_POST["MM_insert"])) && ($_POST["MM_insert"] ==
"form1")) {
$errors = array();
// remove leading and trailing spaces from input
$_POST = array_map('trim', $_POST);
// validate the input
if (empty($_POST['username'])) {
$errors[] = 'Please enter a username';
}
if (strlen($_POST['password']) < 6) {
$errors[] = 'Password must be at least 6 characters';
}
if ($_POST['password'] != $_POST['password2']) {
$errors[] = 'Passwords do not match';
}
// validate the email *** REQUIRES PHP 5.2 ***
if (!filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Please enter a valid email address';
}
// go ahead if no errors are detected
if (!$errors) {
// encrypt the password and generate a unique token
$_POST['password'] = sha1($_POST['password']);
$token = md5(uniqid(mt_rand(), true));
$insertSQL = sprintf("INSERT INTO users (username, password,
email, token) VALUES (%s, %s, %s, %s)",
GetSQLValueString($_POST['username'], "text"),
GetSQLValueString($_POST['password'], "text"),
GetSQLValueString($_POST['email'], "text"),
GetSQLValueString($token, "text"));
mysql_select_db($database_cookbook, $cookbook);
$Result1 = mysql_query($insertSQL, $cookbook);
}
}
Most of what's going on here is explained in the inline
comments.
$errors is initialized as an empty array, and suitable
error messages are added to it if any field fails validation. It's
important to note that the email is validated using
filter_input(), which requires a minimum of PHP 5.2.
Since support for PHP 4 was dropped in August 2008, this should not
be a problem on an up-to-date server.
If no errors are detected, the password is encrypted using
sha1(), and the token is generated using a combination
of three PHP functions. If you are interested in the details, see
the
uniqid()
documentation in the PHP online manual.
Other points to note are that
$_POST['token'] has been replaced by
$token in the script generated by Dreamweaver, and the
or die(mysql_error()) clause has been deleted from the
final line before the closing braces.
Using a UNIQUE index for the
username column results in MySQL throwing an error if
anyone attempts to insert the same value twice. Let's handle that
next. Insert the following code immediately after the line
beginning
$Result1, and before the closing braces:
// generate an error message if the username is already in use
if (!$Result1 && mysql_errno() == 1062) {
$errors[] = $_POST['username'] . ' is already in use. Please
choose a different username.';
} elseif (mysql_error()) {
$errors[] = 'Sorry, there was a problem with the database. Please
try later.';
} else {
// if all OK, send an email to the user
}
If a duplicate entry is detected, MySQL abandons the insert
process and returns error 1062, so a suitable message is added to
the
$errors array. If any other error occurs, a more
general message is created instead.
As long as there are no errors, you can send the email message requesting validation of the email address.
Add the following code under the comment in the final
else clause in the preceding code block:
$to = $_POST['email']; $subject = 'Registration confirmation'; $message = 'Please click the following link or copy and paste it into your browser to complete the registration process: '; $message .= 'http://example.com/confirm.php?u='; $message .= urlencode($_POST['username']); $message .= '&t='; $message .= urlencode($token); $from = 'registration@example.com'; mail($to, $subject, $message, $from);
This represents a very basic message, which you can elaborate.
You should probably also add instructions to ignore the message if
the recipient didn't register at your site. The important point to
note is that it contains the URL of the confirmation page. A query
string with two variables,
u and
t, is appended to the URL. The values assigned to
u and
t are
$_POST['username'] and
$token respectively. Both values are passed to the PHP
function
urlencode() to encode any characters that would be
invalid in a URL.
The final stage of this registration process is to tell the user what has happened, and if the registration was successful, to expect the email. Add this code above the form in the body of the page:
<?php
if ($_POST && $errors) {
echo '<ul>';
foreach ($errors as $error) {
echo "<li>$error</li>";
}
echo '</ul>';
} elseif ($_POST && !$errors) {
echo 'Thank you for registering. A confirmation email has been
sent to the address you supplied. Follow the instructions in the
email to complete the registration process.';
}
?>
If the form has been submitted and errors were found, these are displayed as a bulleted list. Otherwise, the user is told to expect a confirmation email.
Finally, remove the hidden form field for
token, as it is no longer needed.
The final code for this page is in registration3.php in the zip file attached below.
In part 2 of this recipe, you'll build the confirmation page that updates the user's status in the database if a valid username and token are submitted.
+