How to Handle Email Verification in Laravel

On January 22, 2020
5min read
Aleksandr Varnin Full Stack Developer @ Railsware

Updated on May 8, 2023.

When a new user clicks on the Sign-up button of an app, they usually get a confirmation email with an activation link. This is needed to make sure that the user owns the email address entered during the sign-up. After they click on the activation link, the user is authenticated for the app.

From the user’s standpoint, the email verification process is quite simple. From the developer’s perspective, things are much trickier unless your app is built with Laravel. Those who use Laravel 5.7+ have the user email verification available out-of-the-box. For earlier releases of the framework, you can use a dedicated package to add email verification to your project. However, most of those packages are no longer maintained and could have significant security vulnerabilities.

So, in this article, we’ll detail how to do email verification automatically and manually in newer versions of Laravel. We’ll also discuss some customization options. Let’s begin.

Basic project to be used as an example

Since email verification requires one to send emails in Laravel, let’s create a basic project with all the stuff needed for that. Here is the first command to begin with:

composer create-project --prefer-dist laravel/laravel app

Now, let’s create a database using the mysql client and then configure the .env file thereupon:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=DB-laravel
DB_USERNAME=root
DB_PASSWORD=root

Run the migrate command to create tables for users, password resets, and failed jobs:

php artisan migrate

Since our Laravel app will send a confirmation email, we need to set up the email configuration in the .env file.

For email testing purposes, we’ll use Mailtrap Email Testing, an Email Sandbox that captures SMTP traffic from staging and allows developers to debug emails without the risk of spamming users.

The Email Sandbox is one of the SMTP drivers in Laravel. All you need to do is sign up and add your credentials to .env, as follows:

MAIL_MAILER=smtp  
MAIL_HOST=smtp.mailtrap.io  
MAIL_PORT=2525  
MAIL_USERNAME=<********> //Your Mailtrap username  
MAIL_PASSWORD=<********> //Your Mailtrap password
MAIL_ENCRYPTION=tls

For more on Mailtrap features and functions, read the Mailtrap Getting Started Guide.

Scaffold UI

In the latest release of the framework it is possible to use several starter kits to scaffold the UI for registration, login and forgot password. We will use Laravel Breeze which includes all authentication features such as login, registration, password reset, email verification, and password confirmation.

You can install Laravel Breeze using composer: composer require laravel/breeze --dev

The next step is to run php artisan breeze:install – this command will publish the code (views, routes, controllers) to your application.

Finally, you will need to run migrations and install frontend assets:

php artisan migrate

npm install

npm run dev

Now you can navigate to /login and /register routes.

Set up email verification in Laravel 10 using the MustVerifyEmail contract

The Must Verify Email contract is a feature that allows you to send email verification in Laravel by adding a few lines of code to the following files:

App/User.php:

Implement the MustVerifyEmail contract in the User model:

<?php

namespace App;

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

class User extends Authenticatable implements MustVerifyEmail
{
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];


    protected $hidden = [
        'password', 'remember_token',
    ];
}

routes/web.php

Add routes such as email/verify, email/verify/{id}/{hash}, and email/verification-notification to the app:

use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
Route::get('/email/verify', function () {
    return view('auth.verify-email');
})->middleware('auth')->name('verification.notice');
 
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
    $request->fulfill();
 
    return redirect('/dashboard);
})->middleware(['auth', 'signed'])->name('verification.verify');
 
Route::post('/email/verification-notification', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();
 
    return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');

Now you can test the app. 

And that’s what you’ll see in the Mailtrap Demo inbox:

Customization

In the screenshots above, the default name of the app, Laravel, is used as the sender’s name. You can update the name in the .env file:

APP_NAME=<Name of your app> 

To customize notifications, you need to call the toMailUsing method from the boot method of your application’s App\Providers\AuthServiceProvider class.

use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;
 
/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    // ...
 
    VerifyEmail::toMailUsing(function (object $notifiable, string $url) {
        return (new MailMessage)
            ->subject('Verify Email Address')
            ->line('Click the button below to verify your email address.')
            ->action('Verify Email Address', $url);
    });
}

For more on sending Laravel email notifications, read our dedicated blog post

How can I manually verify users?

The MustVerifyEmail class is a great thing to use. However, you may need to take over the control and manually verify email addresses without sending emails. Why would anyone do so? Reasons may include a need to create and add system users that have no accessible email addresses, import a list of email addresses (verified) to a migrated app, and others. 

So, each manually created user will see the following message when signing in:

The problem lies in the timestamp in the Email Verification Column (email_verified_at) of the user table. When creating users manually, you need to validate them by setting a valid timestamp. In this case, there will be no email verification requests. Here is how you can do this:

markEmailAsVerified()

The markEmailAsVerified() method allows you to verify the user after it’s been created. Check out the following example:

$user = User::create([
    'name' => 'John Doe',
    'email' => 'john.doe@example.com',
    'password' => Hash::make('password')
]);

$user->markEmailAsVerified();

forceCreate()

The forceCreate() method can do the same but in a slightly different way:

$user = User::forceCreate([
    'name' => 'John Doe',
    'email' => john.doe@example.com',
    'password' => Hash::make('password'),
    'email_verified_at' => now() //Carbon instance
]);

Manually set a valid timestamp

The most obvious way is to set a valid timestamp in the email_verified_at column. To do this, you need to add the column to the $fillable array in the user model. For example, like this:

protected $fillable = [
    'name', 'email', 'password', 'email_verified_at',
];

After that, you can use the email_verified_at value within the create method when creating a user:

$user = User::create([
    'name' => 'John Doe',
    'email' => john.doe@example.com',
    'password' => Hash::make('password'),
    'email_verified_at' => now() //Carbon instance
]);

Laravel queuing for email verification

The idea of queuing is to dispatch the processing of particular tasks, in our case, email sending, until a later time. This can speed up processing if your app sends large amounts of emails. It would be useful to implement email queues for the built-in Laravel email verification feature. The simplest way to do that is as follows:

  • Create a new notification, e.g., CustomVerifyEmailQueued, which extends the existing one, VerifyEmail. Also, the new notification should implement the ShouldQueue contract. This will enable queuing. Here is how it looks:
namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Auth\Notifications\VerifyEmail;

class CustomVerifyEmailQueued extends VerifyEmail implements ShouldQueue
{
    use Queueable;

}
  • Then override sendEmailVerificationNotification on the User model, just like we did in the Customization block above.
public function sendEmailVerificationNotification()
{
    $this->notify(new \App\Notifications\CustomVerifyEmailQueued);
}

We did not touch upon the configuration of the queue driver here, which is “sync” by default without actual queuing. If you need some insight on that, check out this guide to Laravel email queues.

To wrap up

Sending a verification email is the most reliable way to check the validity of an email address. The tutorials above will help you implement this feature in your Laravel app. At the same time, if you need to validate a large number of existing addresses, you do not have to send a test email to each of them. There are plenty of online email validators that will do the job for you. In the most extreme case, you can validate an email address manually with mailbox pinging. For more on this, read this blog post.

Article by Aleksandr Varnin Full Stack Developer @ Railsware

Comments

3 replies

Hendi Santika

Its very helpful tutorial.

I think it would be better to put all the code project in GitHub Repository.

So that We can compare our code.

Thanks

Sagar Nagendra

Thank you. Its very helpful.

Jesus

Hi. Is this still good practice? What about anti virus software that visits all links in an email. Will opening the email by itself activate the account? (without clicking on the activate button)

Thanks.

Comments are closed.