OneSheep

Email Verification middleware with Laravel

Having worked with Laravel in our previous projects one of the things we have found ourselves implementing time and again is email verification (after a user has registered, we want to send them a link to click to make sure their email address is valid). Laravel is awesome in too many ways to mention and enabling email verification is a real breeze. In this post, my assumption is we are working with a barebones Laravel installation, using App\User as the model for our users.

Preparing the database

For verification to work we need to run a few migrations i.e create a few columns in the Users table to cater for the extra fields. The fields we are going to need are:

  1. Verification code field which contains the string a user verifies via email
  2. A field that tells us if a user is verified or not

Let’s go ahead and create the migration:

php artisan make:migration add_verification_fields_to_user_table --table=users

My migration looks like this:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddVerificationFieldsToUserTable extends Migration
{
   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
       Schema::table('users', function (Blueprint $table) {
           $table->string('verification_code', 10);
           $table->boolean('verified')->default(0)->nullable();
       });
   }

   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
       Schema::table('users', function (Blueprint $table) {
           $table->dropColumn(['verification_code', 'verified']);
       });
   }
}

Now that we have our fields set up, let’s migrate to create the new fields:

php artisan migrate

IMPORTANT: Don’t forget to add the new fields to the $fillable array on the user model.

Creating and requiring user accounts to be verified

We need to create the verification token for every account so let’s hook into the Auth controller’s create method to insert it into the database.

protected function create(array $data)
{
   return User::create([
       'name' => $data['name'],
       'email' => $data['email'],
       'password' => bcrypt($data['password']),
       'verification_code' => str_random(10)
   ]);
}

When we think about verifying accounts, one is inclined to think just after a user registers we email them to validate the address. Whilst true and an option that would work, I haven’t chosen this option too many times because sometimes we tack on validation as an additional, requested-by-client feature. If we focus just on new accounts, what happens to the accounts already registered possibly with wrong email addresses? Introducing verification middleware.

What a middleware layer allows us to do in Laravel is to act as a gatekeeper to route(s) we choose. For example, we could decide an unverified user can browse blog posts without shopping on the website. Without further ado let’s create our middleware:

php artisan make:middleware FilterVerifiedUsers

Now in App/Http/MiddleWare/FilterVerifiedUsers.php we enter the following:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class FilterVerifiedUsers
{
   /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request $request
    * @param  \Closure $next
    * @return mixed
    */
   public function handle($request, Closure $next)
   {

       $user = Auth::user();

       if (!$user->verified) {
           $user->sendVerificationEmail();
           return redirect('/verifyemail')->withErrors(['Account is not yet verified']);
       }

       return $next($request);
   }

We are checking if the verified user field is not true, in which case we call a method on the user model sendVerificationEmail which creates a token and sends an email to the user’s email address. Having this method in our model makes it easy to test this functionality on its own. Let’s quickly create the method in our User model:

/**
*
* Send a verification email with link
*
* @return void
*/
public function sendVerificationEmail()
{
  
//optionally check if the user has a verification code here

   Mail::send('emails.userverification',
     ['verification_code' => $this->verification_code],
     function ($message) {
         $message->to($this->email)
           ->subject('Please verify your email');
         return true;
     });

}

Take note, the send method on the Mail facade takes the name of a blade to use as email template, some variables and the information about the email like where it is going, subject, etc as the third parameter. Read more about Mail here.

In the blade emails.userverification, we are receiving the verification code which we are going to use to create a link that a user clicks on and gets their account verified. The important part of that blade will read as follows:

url(‘user/verifyemail/{{ $verification_code }}’);

This essentially creates a link that will take the user to the route user/verifyemail/_their_token_here. We are going to define our routes in the last section so hang on.

Adding Middleware to Kernel.php

For our middleware to be recognized as such by Laravel, we need to add it to kernel.php in the $routeMiddleware array:

protected $routeMiddleware = [
 'auth' => \App\Http\Middleware\Authenticate::class,
 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
 'verified' => \App\Http\Middleware\FilterVerifiedUsers::class,
 ];

Defining routes

I could have started by defining routes but I wanted a bottom-up approach to showcase how this works before everything comes together. In our routes.php is where we are going to define which routes use this FilterVerifiedUsers middleware and which ones handle the verification itself. I’m going to assume we want unverified users to see blog posts and not shop on the website. All unverified users will be sent to a page at /verifyemail with a message to check their email for link and verification happens at /user/verifyemail/{{code}}

//allow all authenticated users to view blog posts
Route::group(['middleware' => 'auth'], function () {
   Route::get('blog');
});

//allow authenticated AND verified users only to shop on website
Route::group(['middleware' => 'auth', 'verified'], function () {
   Route::get('shop');
});

//denied verification route
Route::get('verifyemail', function(){
   return "Please check your email to verify your email address and start shopping";
});

//Handle verification
Route::get('user/verifyemail/{{code}}', ‘Auth\Controller@verifyEmail’);

Verifying an account

After receiving an email with a link and clicking on it, a user is taken to the route:user/verifyemail/random_string_code_here which is handled according to our route definition by verifyEmail on the Auth Controller. Let’s add that in:

public function verifyEmail(Request $request, $verificationCode)
{

   //check if verificationCode exists
   if (!$valid = User::where('verification_code', $verificationCode)->first()) {
       return redirect('/login')->withErrors([‘that verification code does not exist, try again’]);
   }

   $conditions = [
     'verified' => 0,
     'verification_code' => $verificationCode
   ];

   if ($valid = User::where($conditions)->first()) {

$valid->verified = 1;
$valid->save();

       return redirect('/login')
         ->withInput(['email' => $valid->email]);
   }

   return redirect('/home')->with('message',’Your account is already validated’);
}

If you’d like to trigger email verification email right after registration, you can easily make use of the sendVerificationEmail method on the user model, calling it right after the create function of the auth controller; to check if a user is verified after login, hook into the authenticated method in the Auth controller.


Posted on Nov 15, 2016 by Chiko Mukwenha

Back to all posts