Laravel makes it pretty easier to implement email verification. The default auth scaffolding that comes with Laravel is very flexible and we can easily modify it to include verification functionality.

The overall procedure is also simple:

  • When an user registers, log him out immediately.
  • Register a provider, e.g EloquentEventServiceProvider , that looks for eloquent events, and when an user is created, generate a corresponding token for that user and fire off an event, e.g UserRegistered.
  • If a user requests a verification email to be resent, fire off another event, e.g UserRequestedVerificationEmail.
  • Create an event listener, e.g SendVerificationEmail, that listens for UserRegistered or UserRequestedVerificationEmail event, and then send a email with the verification link using a mailable class.
  • Finally, when the user visits the link sent to email, match the token from the url with the one in the database and on successful matching, set the verified flag to true.
  • When an user tries to log in, check if the verified flag is true. If not, redirect him back to login.

TL;DR: A complete implementation can be found on [this Github repo](https://github.com/shamilchoudhury/laravel-user-email-verification) 🙂

Setting up the migrations

Quick tip: If you’re using a fresh installation of Laravel 5.5, use Laravel’s default auth scaffolding: php artisan make:auth


We will add just one column verified to the users table. This will be a boolean field and we will set it to false by default.

$table->boolean('verified')->default(false);

Next, we will create a new table verification_tokens that will hold the verification token we generate for the user. We will also setup a Foreign Key Constraints with the users table.

$table->integer('user_id')->unsigned()->index(); 
 
$table->string('token');
 
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

Run the migrations and we are good to go.

Setting up the relationship

Relationship between User and VerificationToken is straightforward – a token belongs to an user and an user has one token. We can simply add these relationships to corresponding models.

//User model
public function verificationToken()
{
    return $this->hasOne(VerificationToken::class);
}

and on VerificationToken model

//VerificationToken model
public function user()
{
	return $this->belongsTo(User::class);
}

While we are at it, let’s change the route key name for our VerificationToken model.

public function getRouteKeyName()
{
	return 'token';
}

We will use the token field rather than the id field to do an implicit model binding when we fetch the token from the link.

Setting up the verification controller and the routes

We need a controller that will be used to verify the token and resend the verification email, should the user requests it. We will define two methods:
-the verify() method will receive an instance of VerificationToken model (we will use route binding for this). The actual verification using the token will be done here.
- The resend() method will be used to resend the verification link to the user.


class VerificationController extends Controller
{
    public function verify(VerificationToken $token)
    {
    	//
    }
 
    public function resend(Request $request)
    {
    	//
    }
}

Let’s define two corresponding routes for the VerificationController :

Route::get('/verify/token/{token}', 'Auth\VerificationController@verify')->name('auth.verify'); 
 
Route::get('/verify/resend', 'Auth\VerificationController@resend')->name('auth.verify.resend');

I have created the controller in App\Http\Controllers\Auth namespace. Feel free to create it anywhere within App\Http\Controllers and update the relevant namespaces.

Updating the Auth Controllers

When an user registers an account, by default, he’ll also be logged in immediately. The RegisterController uses the RegistersUsers trait defined in Illuminate\Foundation\Auth . We can override the registered() method from the trait on our RegisterController to perform an immediate logout.

protected function registered(Request $request, $user)
{
    $this->guard()->logout();
 
    return redirect('/login')->withInfo('Please verify your email');
}

The guard() method is also defined on the RegistersUsers trait that fetches the current guard for the logged in user and performs the logout. We then redirect the user to the login page with appropriate message.

We also need to ensure that a user can not login without verifying the email first. There are many ways to accomplish this. However, the simplest way is to override the authenticated() method from AuthenticatesUsers trait in our LoginController.

protected function authenticated(Request $request, $user)
{
    if(!$user->hasVerifiedEmail()) {
        $this->guard()->logout();
 
        return redirect('/login')
            ->withError('Please activate your account. <a href="' . route('auth.verify.resend') . '?email=' . $user->email .'">Resend?</a>');
    }
}

If the $user is not verified, we would log him out immediately and redirect him to login page with an error message. We are also injecting the user’s email in the query string that we can later use to fetch the user by email when resending the verification link.

When a user resets the password, on successful reset, by default laravel automatically signs them in. If a user has not verified the email, we do not want them to be able to sign in. But they should be able to reset the password. This sounds complicated, but is really simple.

The ResetPasswordController uses the ResetsPasswords trait from Illuminate\Foundation\Auth, which implements sendResetResponse() method to check whether a reset has been successful. We can override this method in ResetPasswordController to do our verified account check just before the response is set.

Because on a successful password reset the user will be logged in, we can call the guard(), grab the user and check if they already verified their email. If they are not verified, we would log them out immediately and redirect them back to the login page with an appropriate message.

protected function sendResetResponse($response)
{
    if(!$this->guard()->user()->hasVerifiedEmail()) {
        $this->guard()->logout();
        return redirect('/login')->withInfo('Password changed successfully. Please verify your email');
    }
    return redirect($this->redirectPath())
                        ->with('status', trans($response));
}

However, if the user is already verified, they would be redirected to application’s home authenticated view.


Registering a Service Provider for Eloquent Events

Now create a service provider and register it in the config\app.php . This service provider that will look for a specific Eloquent event: created on User model. The created eloquent event also receives a instance of the model that has been created, in this case the User model.

public function boot()
{
    User::created(function($user) {
 
        $token = $user->verificationToken()->create([
            'token' => bin2hex(random_bytes(32))
        ]);
 
        event(new UserRegistered($user));
    });
}

When an user is created, we want to generate a token for that user. We can use PHP 7’s shiny new random_bytes() function to generate a 64 characters long random string. Finally a new UserRegistered event is fired.


Creating UserRegistered event

Let’s create the UserRegistered event we have used in above snippet.

The whole idea behind using events and listeners here is that apart from firing a verification email, we may later also want to send a welcome email, or a do some payment stuff when an user registers. We can easily hook all that up without much refactoring.

Since our UserRegistered event is receiving the created User instance, we can assign this to a public property so that our listener can pick it up.

public $user;
 
public function __construct(User $user)
{
    $this->user = $user;
}

Now we have the flexibility to listen when this event is fired and do multiple stuff.


Creating UserRequestedVerificationEmail event

But what if the user requests to resend the verification email? Let’s create another event for that: UserRequestedVerificationEmail . This event will also receive a User instance, so we can assign this to a public property for our listeners to pick it up, just like we did with the UserRegistered event.

public $user;
 
public function __construct(User $user)
{
    $this->user = $user;
}

That’s all we need for this event. Let’s jump into creating a listener.


Creating a Listener

Okay. Now that we are done with the events, let’s create a listener: SendVerificationEmail.

This listener has only one job – whenever a UserRegistered or UserRequestedVerificationEmail event is fired, pick up the user and send a new email with the verification link.

public function handle($event)
{
    Mail::to($event->user)->send(new SendVerificationToken($event->user->verificationToken));
}

The handle() method receives an event instance and from that we can extract the user and access the token associated with that user using the relationship. SendVerificationToken is the mailable class we are using to send email.

Finally map the event listener in EventServiceProvider.

protected $listen = [
    'App\Events\UserRegistered' => [
        'App\Listeners\SendVerificationEmail',
    ],
    'App\Events\UserRequestedVerificationEmail' => [
        'App\Listeners\SendVerificationEmail',
    ],
];

Now our event/listener setup is complete.


Creating a mailable class

Because any public property in Laravel’s mailable class is automatically available in the view, very simple we can set a $token property that contains the verification token and then use that in the mail view.

public $token;
 
public function __construct(VerificationToken $token)
{
    $this->token = $token;
}

The build() method is also very simple.

public function build()
{
    return $this->subject('Please verify your email')
                ->view('email.auth.verification');
}

Finally we create the view:

To verify your account, visit the following link. <br> <br>
 
<a href="{{ route('auth.verify', $token) }}">Verify now</a>

We are using the route() helper method to generate the link for us. Since we are using laravel’s implicit model binding, the $token will be used to generate a valid link automatically.


Verifying the User

In our verify() method in VerificationController , using the route model binding, the model instance that has a token matching the corresponding value from the request URI will be automatically injected ( laravel <3 ) . So we can access the user associated with that token and update the verified field.

$token->user()->update([
	'verified' => true
]);

We do not need the token anymore.

$token->delete();

Then we can simply redirect them to the login page so they can login again using their email/password.

public function verify(VerificationToken $token)
{
	$token->user()->update([
		'verified' => true
	]);
 
	$token->delete();
 
    // Uncomment the following lines if you want to login the user 
    // directly upon email verification
	// Auth::login($token->user);
    // return redirect('/home');
 
	return redirect('/login')->withInfo('Email verification succesful. Please login again');
}

What we can also do is login the user automatically and redirect them to home page or profile page. Update it as per your need.


Resending verification mail

In our resend() method, since when the user requests resending verification email, we are passing his email in the query string, we can easily grab the email from the request and fetch the user.

$user = User::where('email', $request->email)->firstOrFail();

Notice the use of firstOrFail() , because if an user with that email is not found, this will throw a Illuminate\Database\Eloquent\ModelNotFoundException. (In a real world application, you might want to catch that exception and render a custom error message. If the exception is not caught, laravel will throw a simple 404 error).

We now need to check if the user is already verified. If the user has already verified the email, we do not want to send a verification link.

if($user->hasVerifiedEmail()) {
    return redirect('/home')->withInfo('Your email has already been verified');
}

Since we created a token when the user registered, we do not need to recreate it. Therefore, to resend the verification email, we simply fire off the UserRequestedVerificationEmail event. Since we already set it up along with a listener, much of the work has been done.

event(new UserRequestedVerificationEmail($user));

That’s all we need. Now we can redirect them with the appropriate message.

public function resend(Request $request)
{
	$user = User::byEmail($request->email)->firstOrFail();
 
    if($user->hasVerifiedEmail()) {
        return redirect('/home');
    }
 
    event(new UserRequestedVerificationEmail($user));
 
    return redirect('/login')->withInfo('Verification email resent. Please check your inbox');
}

The complete resend() method is shown above.


Conclusion

Laravel’s event listeners implements a observer implementation which are extremely useful for solving problems like this. Breaking the whole procedure into providers, events and listeners makes the overall architecture very flexible. Even if we are using a custom authentication instead of the default auth scaffolding, it is still easier to implement and test new features.