How to extend Laravel default Authentication with Email Verification


Every Laravel project is shipped with a simpler default User Authentication System which is very easy to set up. It uses user's email and password for the sign-in purpose. But the default one misses one simple functionality that is we also need to verify user email before letting them use our application which is pretty much standard. In this tutorial, I will show how to easily extend the laravel default Authentication System to support Email Verification without using any third party libraries.

Let's Start Coding

1) Configure your Env File

First and foremost we need to add mail service provider details in our env file. For testing purpose, it is recommended to use Mailtrap so that the emails will not be sent to actual users. The Mailtrap is a fake SMTP server for development teams to test, view and share emails sent from the development and staging environments without spamming users. It is free and easy to set up. Just sign up to https://mailtrap.io/.

2) Run Default Authentication

Since we are extending laravel default Authentication system let's initialise it by running two commands.

php artisan make:auth

php artisan migrate

Congrats you have successfully created laravel default authentication system.  Now let's extend it to support email verification too.

3) Configure your routes file

Add the following route to your web.php file

//Email Verification
Route::get('/verifyemail/{token}', 'Auth\RegisterController@verify');

 

4) Create a new migration

Now we need to extend our users table to add two more fields ie verified flag and email_token to store a unique 60 character token. The easiest way to do this is to create a migration file.

php artisan make:migration add_email_verification_to_user_table --table=users

The contents of migration file are given below.

<?php

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

class AddEmailVerificationToUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->tinyInteger('verified')->default(0)->after('password');
            $table->string('email_token',60)->nullable()->after('verified');
        });
    }

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

 

Now let's run the new migration by running 

php artisan migrate

5) Modify User Model

Since we add two additional fields to our user model let's modify it to support them properly. The complete code of User model is given below.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password','email_token'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token','email_token'
    ];
}

 

We added email_token to the fillable so that we can use mass assignment methods like create and update later in our controller for the simplecity purpose. The email_token is also given in hidden so that it will not be outputted later in any api requests which you may create later in your project. 

6) Create a new Mailable for Email Verification

In Laravel, each type of email sent by your application is represented as a "mailable" class. These classes are stored in the app/Mail directory. So let's create a mailable for sending our verification email by running the following command.

php artisan make:mail EmailVerification

The complete code inside the EmailVerification.php file is given below.

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailVerification extends Mailable
{
    use Queueable, SerializesModels;

    protected $user;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($user)
    {
        $this->user = $user;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->markdown('emails.verification-email')->with(['email_token' => $this->user->email_token]);
    }
}

 

The user collection is injected into the constructor. If you are not familiar with dependency injection then just ignore it. I use markdown instead of view function because I am going to take advantage of the pre-built templates and components of mail notifications in your mailables. Since the messages are written in Markdown, Laravel is able to render beautiful, responsive HTML templates for the messages while also automatically generating a plain-text counterpart. But if you are not using any of laravel's pre-built mail templates and going to use your email template then use the view instead of markdown.

7) Create Email Template using Laravel's Markdown Mailables

Now let's create a verification-email.blade.php file inside views/emails folder. This file is used for marking down our email template. The contents of the file are given below.

@component('mail::message')
	
Click the button below to verify your email address and finish setting up your profile.

@component('mail::button', ['url' => url('/verifyemail/'.$email_token) ])
Verify Email Address
@endcomponent

Thanks,<br>
{{ config('app.name') }}
@endcomponent

 

The actual email output of this view file is given below.

laravel email verification demo image - shareurcodes 

8) Extend Default Auth Controllers

Now let's extend our default auth controllers to support email verification process. Let's first configure RegisterController. The complete code is given below.

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

use Mail;
use Illuminate\Http\Request;
use App\Mail\EmailVerification;
use Illuminate\Auth\Events\Registered;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'email_token' => bin2hex(openssl_random_pseudo_bytes(30)),
        ]);
    }

    /**
    * Handle a registration request for the application.
    *
    * @param \Illuminate\Http\Request $request
    * @return \Illuminate\Http\Response
    */

    public function register(Request $request)
    {

        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $email = new EmailVerification($user);

        Mail::to($user->email)->send($email);

        return view('auth.emails.verification');

    }

    /**
    * Handle a email verification request for the application.
    *
    * @param $token
    * @return \Illuminate\Http\Response
    */

    public function verify($token)
    {
        if ( ! $token)
        {
            return  redirect('login')->with('flash-error','Email Verification Token not provided!');
        }


        $user = User::where('email_token',$token)->first();


        if ( ! $user)
        {
            return  redirect('login')->with('flash-error','Invalid Email Verification Token!');
        }

        $user->verified = 1;

        if ($user->save()) {

            return view('auth.emails.emailconfirm',['user'=>$user]);

        }

    }
}

 

First we use our needed dependencies Mail, Request, EmailVerification, Registered at top of our controller. Don't forget to add these as it will trigger class not found exception.

In our create function we created our 60 characters long unique email_token using PHP's openssl_random_pseudo_bytes function which will generate a pseudo-random string of bytes. We change binary to hexadecimal so that we will get numbers and alphabets instead of zero's and one by using PHP's bin2hex function. Since we change 30 binary characters to hexadecimal now it will become a unique 60 character's token.

Now let's override the laravel's default registration function by creating register method inside our controller. Just after registration instead of logging in the user, we send the verification email by creating an object of EmailVerification Class (The one we created above) and send mail using Laravel's Mail Method.

The verify function is used to verify the email address. When the user clicks on verification button in the email they will be redirected to this function. This function will set the verified flag to one. If the user provides the wrong token I send him back to the login page with a flash error. Just modify your login view page to support this by pasting below code.

@if(Session::has('flash-error'))
     <div class="alert alert-danger alert-dismissible">
          <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
          {{ Session::get('flash-error') }}
      </div>
@endif

 

Now let's configure our LoginController to prevent the unverified user from signing in. The complete code for login controller is given below

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        return ['email' => $request->{$this->username()}, 'password' => $request->password, 'verified' => 1];
    }
}

 

Here also we override laravel's default function to check credentials to support our verified flag which set to 1 only when the email is verified. Don't forget to use Request Class at top of controller else you will get a class not found exception.

9) Create New Views

The last and final step is to create two view files, one is needed after user registration and one is needed after user email verification.

The completed code for the verification.blade.php file is given below.

@extends('layouts.app')
@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Registration Confirmed</div>

                <div class="panel-body">

                    You have successfully registered. An email is sent to you for verification.

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

 

Now the complete code for the emailconfim.blade.php file is given below.

@extends('layouts.app')
@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Registration Confirmation</div>
                
                <div class="panel-body">

                    Your Email is successfully verified. Click here to <a href="{{ url('/login') }}">login</a>

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

 

I just created it by modifying basic laravel home page. Feel free to beautify it according to your application template.

That's it we have successfully extended the Laravel's Default Authentication system to support Email Verification. I tried this on both Laravel 5.4 and Laravel 5.5 projects and it works perfectly fine for me. If anybody has any suggestions or doubts or need any help comment below and I try will respond to every one of you as early as possible.





Web development
19 Nov 2017 12:20pm
PHP Laravel
1002