Email verification on register with Laravel

This article will demonstrate email verification functionality on user registration in Laravel. To verify the user's email you need to send an email to the user with a link for verification. In this demonstration, we will create a token that attaches to the verification link to prevent unauthorize verify as well as use the token to identify the verified user. Assuming you already have table users with the following fields id, name, email, email_verified_at, password, created_at, updated_at.

Create email_verifies table & model

To create tables in Laravel you can use migration. First, create a migration file follow the command below.

php artisan make:migration create_email_verifies_table

Now go to database/migrations/{{timestamp}}_create_email_verifies_table.php

<?php

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

class CreateEmailVerifiesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('email_verifies', function (Blueprint $table) {
            $table->id();
            $table->string('email');
            $table->string('token');
            $table->timestamp('expired_at');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('email_verifies');
    }
}

Now create model for email_verifies table with command below.

php artisan make:model EmailVerify

You will see EmailVerify.php model in the app/Models/EmailVerify.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class EmailVerify extends Model
{
    protected $table = 'email_verifies';
    protected $fillable = ['email', 'token', 'expired_at'];
}

Create controller for register user & verify email

php artisan make:controller AuthController

The make:controller command will create a controller in the app/Http/Controllers, you will see the AuthController.php in the app/Http/Controllers/AuthController.php directory.

Noted that the registered user doesn’t have validation yet so if you register a user with the same email and the user migration put the email to unique it will throw SQL error on duplicate entry.

The register method will create a user with a hash password then generate a random token with a length of 255 and store it in the email_verifies table along with email and expired_at. This demonstrates the verify token will expire in 5 minutes as you can see the expired_at with the Carbon::now()->addMinutes(5). Then we sent an email to the registered user email address with the verification link.

The verifyEmail method will check if the token is valid, or expired and if the token is valid it will then confirm that the user email is verified by putting the current timestamp to email_verified_at.

In the app/Http/Controllers/AuthController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\EmailVerify;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Mail\EmailVerificationMail;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        DB::beginTransaction();
        try {
            $user = $request->only('name', 'email', 'password');
            $user['password'] = Hash::make($user['password']);
            User::create($user);


            $emailVerify = EmailVerify::updateOrCreate(
                ['email' => $user['email']],
                [
                    'token' => Str::random(255), 
                    'expired_at' => Carbon::now()->addMinutes(5)
                ]
            );
            Mail::to($user['email'])->send(
                new EmailVerificationMail(route('mail.verify', ['token' => $emailVerify->token]))
            );
            DB::commit();
            return response()->json([ 'success' => true ]);
        } catch (\Throwable $th) {
            DB::rollBack();
            throw $th;
        }
    }

    public function verifyEmail(Request $request)
    {
        $emailVerify = EmailVerify::where('token', $request->token)->first();
        if (!$emailVerify) {
            return response()->json([ 'message' => 'Invalid verification link' ]);
        }
        if (Carbon::now()->greaterThan($emailVerify->expired_at)) {
            return response()->json([ 'message' => 'Verification link expired' ]);
        }
        $user = User::where('email', $emailVerify->email)->first();
        $user->update(['email_verified_at' => Carbon::now()]);
        $emailVerify->delete();
        return response()->json([ 'message' => 'Your account has been verified' ]);
    }
}

Create mail template and configuration

Simply type the command below will generate a EmailVerificationMail.php in the app/Mail/EmailVerificationMail.php

php artisan make:mail EmailVerificationMail

In the app/Mail/EmailVerificationMail.php

<?php

namespace App\Mail;

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

class EmailVerificationMail extends Mailable
{
    use Queueable, SerializesModels;
    private $link;

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

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('Mail verification')
            ->markdown('emails.mail-verification', [
                'link' => $this->link
            ]);
    }
}

Create the mail template in the resources/views/emails/mail-verification.blade.php. As we build it with the markdown so we can write markdown style in that view.

@component('mail::message')
# Mail verification
 
Your mail verification link will expire in 5 minutes.
 
@component('mail::button', ['url' => $link])
Verify
@endcomponent
 
Thanks,<br>
codenotfound.dev
@endcomponent

Now config your mail SMTP in the .env file

If you want to send from your Gmail now the new update of google mail you need to have two-factor authentication on your email account in order to create an app password to config in Laravel.

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=yourmail@gmail.com
MAIL_PASSWORD="yourmailapppassword"
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=yourmail@gmail.com
MAIL_FROM_NAME="name"

Route API

In routes/api.php

<?php

use App\Http\Controllers\AuthController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::post('user/register', [AuthController::class, 'register']);
Route::get('user/verify', [AuthController::class, 'verifyEmail'])->name('mail.verify');

Conclusion

Following this article and practice along the way you will get a good understanding of the concept of how to verify user registration email as well as how to implement it with Laravel. You will get from this article the way to register a user, send mail, and generate a random token to verify the user's email.

What if the mail verification link expired? How to generate a new verification link?