Laravel Framework: Authentication – Email Verification with Activation Code
In the previous tutorial, were able to setup basic user authentication through the login and register options. But in a live production environment, you would want to verify that the users actually own and have access to the email addresses they have provided for the system.
This functionality unfortunately doesn’t come bundled in Laravel just like authentication was, but its still easy to implement through existing packages like Jean Ragouin‘s Laravel User Verification package. However, for this tutorial am going to implement the user account email verification functionality from scratch since I found out that its not so generic to be perfectly encapsulated in a package.
#1. Users Table
We are going to add two new columns (verified
, and email_token
) to the already existing Users table
.
Make add_account_verification_to_users
migration
1 |
php artisan make:migration add_account_verification_to_users --table=users |
Edit migration at database/migrations/...add_account_verification_to_users.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddAccountVerificationToUsers extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { $table->tinyInteger('verified')->default(0); $table->string('email_token')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('verified'); $table->dropColumn('email_token'); }); } } |
Complete migration
1 |
php artisan migrate |

Updates ‘users’ table
#2. User Model
Now you need to modify the user model
at app/user.php
as follows;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?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', 'verified', 'email_token', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; // Set the verified status to true and make the email token null, // on user account activation/verification public function verified() { $this->verified = 1; $this->email_token = null; $this->save(); } } |
You will notice that we added verified
and email_token
to the fillable [] array
, as well as add the verified () method
to the model. The new verified method will update the user field in the database upon verification.
#3. Email Verification
Create the following class app/email/EmailVerification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?php namespace App\Mail; use App\User; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Queue\ShouldQueue; class EmailVerification extends Mailable { use Queueable, SerializesModels; public $user; /** * Create a new message instance. * * @return void */ public function __construct(User $user) { $this->user = $user; } /** * Build the message. * * @return $this */ public function build() { return $this->view('emails.verification'); } } |
#3.1 EMAIL MESSAGE TEMPLATE
Now create the email verification template at resources/views/emails/verification.blade.php
as follows;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <style type="text/css" rel="stylesheet" media="all"> /* Media Queries */ @media only screen and (max-width: 500px) { .button { width: 100% !important; } } </style> </head> <?php $style = [ /* Layout ------------------------------ */ 'body' => 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;', 'email-wrapper' => 'width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;', /* Masthead ----------------------- */ 'email-masthead' => 'padding: 25px 0; text-align: center;', 'email-masthead_name' => 'font-size: 16px; font-weight: bold; color: #2F3133; text-decoration: none; text-shadow: 0 1px 0 white;', 'email-body' => 'width: 100%; margin: 0; padding: 0; border-top: 1px solid #EDEFF2; border-bottom: 1px solid #EDEFF2; background-color: #FFF;', 'email-body_inner' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0;', 'email-body_cell' => 'padding: 35px;', 'email-footer' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0; text-align: center;', 'email-footer_cell' => 'color: #AEAEAE; padding: 35px; text-align: center;', /* Body ------------------------------ */ 'body_action' => 'width: 100%; margin: 30px auto; padding: 0; text-align: center;', 'body_sub' => 'margin-top: 25px; padding-top: 25px; border-top: 1px solid #EDEFF2;', /* Type ------------------------------ */ 'anchor' => 'color: #3869D4;', 'header-1' => 'margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;', 'paragraph' => 'margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;', 'paragraph-sub' => 'margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;', 'paragraph-center' => 'text-align: center;', /* Buttons ------------------------------ */ 'button' => 'display: block; display: inline-block; width: 200px; min-height: 20px; padding: 10px; background-color: #3869D4; border-radius: 3px; color: #ffffff; font-size: 15px; line-height: 25px; text-align: center; text-decoration: none; -webkit-text-size-adjust: none;', 'button--green' => 'background-color: #22BC66;', 'button--red' => 'background-color: #dc4d2f;', 'button--blue' => 'background-color: #3869D4;', ]; ?> <?php $fontFamily = 'font-family: Arial, \'Helvetica Neue\', Helvetica, sans-serif;'; ?> <body style="{{ $style['body'] }}"> <table width="100%" cellpadding="0" cellspacing="0"> <tr> <td style="{{ $style['email-wrapper'] }}" align="center"> <table width="100%" cellpadding="0" cellspacing="0"> <!-- Logo --> <tr> <td style="{{ $style['email-masthead'] }}"> <a style="{{ $fontFamily }} {{ $style['email-masthead_name'] }}" href="{{ url('/') }}" target="_blank"> {{ config('app.name') }} </a> </td> </tr> <!-- Email Body --> <tr> <td style="{{ $style['email-body'] }}" width="100%"> <table style="{{ $style['email-body_inner'] }}" align="center" width="570" cellpadding="0" cellspacing="0"> <tr> <td style="{{ $fontFamily }} {{ $style['email-body_cell'] }}"> <!-- Greeting --> <h1 style="{{ $style['header-1'] }}"> Hello {{ $user->name }}, </h1> <!-- Intro --> <p style="{{ $style['paragraph'] }}"> Click on the below button to verify your email address </p> <!-- Action Button --> <table style="{{ $style['body_action'] }}" align="center" width="100%" cellpadding="0" cellspacing="0"> <tr> <td align="center"> <?php $actionColor = 'button--blue'; ?> <a href="{{ url('register/verify/'.$user->email_token) }}" style="{{ $fontFamily }} {{ $style['button'] }} {{ $style[$actionColor] }}" class="button" target="_blank"> Verify </a> </td> </tr> </table> <!-- Outro --> <p style="{{ $style['paragraph'] }}"> Thank you </p> <!-- Salutation --> <p style="{{ $style['paragraph'] }}"> Regards,<br>{{ config('app.name') }} </p> </td> </tr> </table> </td> </tr> <!-- Footer --> <tr> <td> <table style="{{ $style['email-footer'] }}" align="center" width="570" cellpadding="0" cellspacing="0"> <tr> <td style="{{ $fontFamily }} {{ $style['email-footer_cell'] }}"> <p style="{{ $style['paragraph-sub'] }}"> © {{ date('Y') }} <a style="{{ $style['anchor'] }}" href="{{ url('/') }}" target="_blank">{{ config('app.name') }}</a>. All rights reserved. </p> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table> </body> </html> |
#4. Register Controller
Will will now implement the functionality for the system to automatically send a verification link the user’s email upon successful registration.
Update the RegisterController
at app/http/Controller/auth/RegisterController.php
as follows;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
<?php namespace App\Http\Controllers\Auth; use DB; use Mail; use App\User; use Validator; use Illuminate\Http\Request; use App\Mail\EmailVerification; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Session; 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 login / 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|max:255', 'email' => 'required|email|max:255|unique:users', 'password' => 'required|min:6|confirmed', ]); } /** * Create a new user instance after a valid registration. * * @param array $data * @return User */ protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), 'email_token' => str_random(10), ]); } /** * Over-ridden the register method from the "RegistersUsers" trait * Remember to take care while upgrading laravel */ public function register(Request $request) { // Laravel validation $validator = $this->validator($request->all()); if ($validator->fails()) { $this->throwValidationException($request, $validator); } // Using database transactions is useful here because stuff happening is actually a transaction // I don't know what I said in the last line! Weird! DB::beginTransaction(); try { $user = $this->create($request->all()); // After creating the user send an email with the random token generated in the create method above $email = new EmailVerification(new User(['email_token' => $user->email_token])); Mail::to($user->email)->send($email); DB::commit(); Session::flash('message','We have sent you a verification email!'); return back(); } catch(Exception $e) { DB::rollback(); return back(); } } // Get the user who has the same token and change his/her status to verified i.e. 0 -> 1 public function verify($token) { // The verified method has been added to the user model and chained here for better readability User::where('email_token',$token)->firstOrFail()->verified(); Session::flash('message','Your account is now active, Please login!'); return redirect('login'); } } |
We have overridden the register () method from the “RegistersUsers” trait so that; after successful registration, we generate a random token to use in the verification email, send the email and notice the user that a verification email has been sent to thier email account.
#4.1 Register Template
Edit the register template at resources/views/auth/register.blade.php
to display a notification message to the user that a verification email has been sent to his email account.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
@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">Register</div> <div class="panel-body"> @if(Session::has('message')) <div class="alert alert-info">{{Session::get('message')}}</div> @else <form class="form-horizontal" role="form" method="POST" action="{{ url('/register') }}"> {{ csrf_field() }} <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}"> <label for="name" class="col-md-4 control-label">Name</label> <div class="col-md-6"> <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus> @if ($errors->has('name')) <span class="help-block"> <strong>{{ $errors->first('name') }}</strong> </span> @endif </div> </div> <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}"> <label for="email" class="col-md-4 control-label">E-Mail Address</label> <div class="col-md-6"> <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required> @if ($errors->has('email')) <span class="help-block"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> </div> <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}"> <label for="password" class="col-md-4 control-label">Password</label> <div class="col-md-6"> <input id="password" type="password" class="form-control" name="password" required> @if ($errors->has('password')) <span class="help-block"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> </div> <div class="form-group"> <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label> <div class="col-md-6"> <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required> </div> </div> <div class="form-group"> <div class="col-md-6 col-md-offset-4"> <button type="submit" class="btn btn-primary"> Register </button> </div> </div> </form> @endif </div> </div> </div> </div> </div> @endsection |
#4.2 ACCOUNT VERIFICATION ROUTE
We also added a verify ()
utility method to the Register Controller
verify the user’s account in the database once the user verifies the account using the link we sent him. It uses the verify method we implemented in the user model above.
Now we need to register it in the routes at routes/web.php
, as its the one a user will follow to verify his/her account
1 |
Route::get('/register/verify/{token}', 'Auth\RegisterController@verify'); |
#5. Login Controller
You will notice that at this stage, the user can still login straight after registration without verifying his account. This so because; we don’t check whether a user account is verified during login so lets fix that too.
Update the RegisterController
at app/http/Controller/auth/RegisterController.php
as follows;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<?php namespace App\Http\Controllers\Auth; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\AuthenticatesUsers; 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']); } public function credentials(Request $request) { return [ 'email' => $request->email, 'password' => $request->password, 'verified' => 1, ]; } } |
We have overridden the credentials () method to required the account to be verified, therefore only verified accounts can be logged into.
#5.1 LOGIN TEMPLATE
You may also wish to notify a user that his account has been verified, during login. Update the Login.blade.php
at resources/views/auth/login.blade.php
as follows;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
@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">Login</div> <div class="panel-body"> @if(Session::has('message')) <div class="alert alert-info">{{Session::get('message')}}</div> @endif <form class="form-horizontal" role="form" method="POST" action="{{ url('/login') }}"> {{ csrf_field() }} <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}"> <label for="email" class="col-md-4 control-label">E-Mail Address</label> <div class="col-md-6"> <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required autofocus> @if ($errors->has('email')) <span class="help-block"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> </div> <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}"> <label for="password" class="col-md-4 control-label">Password</label> <div class="col-md-6"> <input id="password" type="password" class="form-control" name="password" required> @if ($errors->has('password')) <span class="help-block"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> </div> <div class="form-group"> <div class="col-md-6 col-md-offset-4"> <div class="checkbox"> <label> <input type="checkbox" name="remember"> Remember Me </label> </div> </div> </div> <div class="form-group"> <div class="col-md-8 col-md-offset-4"> <button type="submit" class="btn btn-primary"> Login </button> <a class="btn btn-link" href="{{ url('/password/reset') }}"> Forgot Your Password? </a> </div> </div> </form> </div> </div> </div> </div> </div> @endsection |