One of the most common security features during the user registration process is e-mail verification. It is important to create it according to industry best practices in order to avoid potential security risks. Let’s discuss these best practices and see how to create e-mail verification in PHP.
We will start with the registration form:
<form method="post" action="http://mydomain.com/registration/">
<fieldset class="form-group">
<label for="fname">First Name:</label>
<input type="text" name="fname" class="form-control" required />
</fieldset>
<fieldset class="form-group">
<label for="lname">Last Name:</label>
<input type="text" name="lname" class="form-control" required />
</fieldset>
<fieldset class="form-group">
<label for="email">Last name:</label>
<input type="email" name="email" class="form-control" required />
</fieldset>
<fieldset class="form-group">
<label for="password">Password:</label>
<input type="password" name="password" class="form-control" required />
</fieldset>
<fieldset class="form-group">
<label for="cpassword">Confirm Password:</label>
<input type="password" name="cpassword" class="form-control" required />
</fieldset>
<fieldset>
<button type="submit" class="btn">Register</button>
</fieldset>
</form>
And with the following database structure:
CREATE TABLE IF NOT EXISTS `user` (
`id` INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`fname` VARCHAR(255) ,
`lname` VARCHAR(255) ,
`email` VARCHAR(50) ,
`password` VARCHAR(50) ,
`is_active` INT(1) DEFAULT '0',
`verify_token` VARCHAR(255) ,
`created_at` TIMESTAMP,
`updated_at` TIMESTAMP,
);
Once the form has been submitted, we need to validate the input and create a new user:
// Validation rules
$rules = array(
'fname' => 'required|max:255',
'lname' => 'required|max:255',
'email' => 'required',
'password' => 'required|min:6|max:20',
'cpassword' => 'same:password'
);
$validator = Validator::make(Input::all(), $rules);
// If input not valid, go back to registration page
if($validator->fails()) {
return Redirect::to('registration')->with('error', $validator->messages()->first())->withInput();
}
$user = new User();
$user->fname = Input::get('fname');
$user->lname = Input::get('lname');
$user->password = Input::get('password');
// You will generate the verification code here and save it to the database
// Save user to the database
if(!$user->save()) {
// If unable to write to database for any reason, show the error
return Redirect::to('registration')->with('error', 'Unable to write to database at this time. Please try again later.')->withInput();
}
// User is created and saved to database
// Verification e-mail will be sent here
// Go back to registration page and show the success message
return Redirect::to('registration')->with('success', 'You have successfully created an account. The verification link has been sent to e-mail address you have provided. Please click on that link to activate your account.');
Upon registration, the user’s account remains inactive until the e-mail has been verified. This feature confirms that the user is the owner of the entered e-mail address and helps prevent spam, unauthorized e-mail usage and information leaks.
The process is quite simple — when a new user is created, an e-mail containing the verification link is sent to the e-mail address entered during the registration process. The user will not be able to login and use the Web application until he/she clicks on the verification link and confirms the e-mail address.
There are a few things to note regarding the verification link. It should contain a randomly generated token that should be long enough and valid for a specific amount of time in order to make brute-force attacks infeasible. Also, the user identifier should be included, so that a potential attack could not brute-force across multiple accounts.
Let’s see how to generate the verification link in practice:
// We will generate a random 32 alphanumeric string
// It is almost impossible to brute-force this key space
$code = str_random(32);
$user->confirmation_code = $code;
Once is it generated and saved to the database, send it to the user:
Mail::send('emails.email-confirmation', array('code' => $code, 'id' => $user->id), function($message)
{
$message->from('[email protected]', 'Mydomain.com')->to($user->email, $user->fname . ' ' . $user->lname)->subject('Mydomain.com: E-mail confirmation');
});
The content of verification e-mail:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
</head>
<body>
<p style="margin:0">
Please confirm your e-mail address by clicking the following link:
<a href="http://mydomain.com/verify?code=<?php echo $code; ?>&user=<?php echo $id; ?>"></a>
</p>
</body>
</html>
Now let’s check to make sure it is valid:
$user = User::where('id', '=', Input::get('user'))
->where('is_active', '=', 0)
->where('verify_token', '=', Input::get('code'))
->where('created_at', '>=', time() - (86400 * 2))
->first();
if($user) {
$user->verify_token = null;
$user->is_active = 1;
if(!$user->save()) {
// If unable to write to database for any reason, show the error
return Redirect::to('verify')->with('error', 'Unable to connect to database at this time. Please try again later.');
}
// Show the success message
return Redirect::to('verify')->with('success', 'You account is now active. Thank you.');
}
// Code not valid, show error message
return Redirect::to('verify')->with('error', 'Verification code not valid.');
Conclusion
The code shown in this tutorial is for educational purposes and is not thoroughly tested. Please test it before using it in a live Web application. It is written in Laravel framework, but can be easily adjusted to any other PHP framework. Also, the verification code has a time limit of 48 hours and after that it will expire. It would be good to implement a cron job that will remove inactive users with expired verification codes from time to time.