Avg. Rating 4.0

Problem

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.

Solution

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.

Detailed explanation

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.

Setting up the database

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.

Creating the registration form

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 the Insert Record server behavior

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.

Validating the user input

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.

Preventing the same username from being inserted twice

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.

Generating and sending the validation email

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 .= '&amp;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.

Reporting the outcome to the user

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.


+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes